Full Code of barry-ran/QtScrcpy for AI

dev 7b8a9580a698 cached
93 files
388.8 KB
113.3k tokens
56 symbols
1 requests
Download .txt
Showing preview only (413K chars total). Download the full file or copy to clipboard to get everything.
Repository: barry-ran/QtScrcpy
Branch: dev
Commit: 7b8a9580a698
Files: 93
Total size: 388.8 KB

Directory structure:
gitextract_v8zjkv6h/

├── .clang-format
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── macos.yml
│       ├── ubuntu.yml
│       └── windows.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── QtScrcpy/
│   ├── CMakeLists.txt
│   ├── appversion
│   ├── audio/
│   │   ├── audiooutput.cpp
│   │   └── audiooutput.h
│   ├── clang-format-all.sh
│   ├── fontawesome/
│   │   ├── iconhelper.cpp
│   │   └── iconhelper.h
│   ├── groupcontroller/
│   │   ├── groupcontroller.cpp
│   │   └── groupcontroller.h
│   ├── main.cpp
│   ├── render/
│   │   ├── qyuvopenglwidget.cpp
│   │   └── qyuvopenglwidget.h
│   ├── res/
│   │   ├── Info_Mac.plist.in
│   │   ├── QtScrcpy.icns
│   │   ├── QtScrcpy.rc
│   │   ├── i18n/
│   │   │   ├── CMakeLists.txt
│   │   │   ├── en_US.qm
│   │   │   ├── en_US.ts
│   │   │   ├── ja_JP.qm
│   │   │   ├── ja_JP.ts
│   │   │   ├── ko_KR.qm
│   │   │   ├── ko_KR.ts
│   │   │   ├── zh_CN.qm
│   │   │   └── zh_CN.ts
│   │   ├── qss/
│   │   │   └── psblack.css
│   │   └── res.qrc
│   ├── sndcpy/
│   │   ├── sndcpy.apk
│   │   ├── sndcpy.bat
│   │   └── sndcpy.sh
│   ├── ui/
│   │   ├── dialog.cpp
│   │   ├── dialog.h
│   │   ├── dialog.ui
│   │   ├── toolform.cpp
│   │   ├── toolform.h
│   │   ├── toolform.ui
│   │   ├── videoform.cpp
│   │   ├── videoform.h
│   │   └── videoform.ui
│   ├── uibase/
│   │   ├── keepratiowidget.cpp
│   │   ├── keepratiowidget.h
│   │   ├── magneticwidget.cpp
│   │   └── magneticwidget.h
│   └── util/
│       ├── config.cpp
│       ├── config.h
│       ├── mousetap/
│       │   ├── cocoamousetap.h
│       │   ├── cocoamousetap.mm
│       │   ├── mousetap.cpp
│       │   ├── mousetap.h
│       │   ├── winmousetap.cpp
│       │   ├── winmousetap.h
│       │   ├── xmousetap.cpp
│       │   └── xmousetap.h
│       ├── path.h
│       ├── path.mm
│       ├── winutils.cpp
│       └── winutils.h
├── README.md
├── README_zh.md
├── backup/
│   └── myconfig.sh
├── ci/
│   ├── generate-version.py
│   ├── linux/
│   │   ├── build_for_linux.sh
│   │   ├── package_appimage.sh
│   │   └── publish_for_ubuntu.sh.todo
│   ├── lrelease.sh
│   ├── lupdate.sh
│   ├── mac/
│   │   ├── build_for_mac.sh
│   │   ├── package/
│   │   │   ├── dmg-settings.json
│   │   │   ├── package.py
│   │   │   └── requirements.txt
│   │   ├── package_for_mac.sh
│   │   └── publish_for_mac.sh
│   └── win/
│       ├── build_for_win.bat
│       └── publish_for_win.bat
├── config/
│   └── config.ini
├── docs/
│   ├── DEVELOP.md
│   ├── FAQ.md
│   ├── KeyMapDes.md
│   ├── KeyMapDes_zh.md
│   └── TODO.md
└── keymap/
    ├── FRAG.json
    ├── gameforpeace.json
    ├── identityv.json
    ├── test.json
    └── tiktok.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .clang-format
================================================
---
# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto
Language:        Cpp
# BasedOnStyle:  WebKit
# 访问说明符(public、private等)的偏移
AccessModifierOffset: -4
# 开括号(开圆括号、开尖括号、开方括号)后的对齐: Align, DontAlign, AlwaysBreak(总是在开括号后换行)
AlignAfterOpenBracket: AlwaysBreak
# 连续赋值时,对齐所有等号
AlignConsecutiveAssignments: false
# 连续声明时,对齐所有声明的变量名
AlignConsecutiveDeclarations: false
# 左对齐逃脱换行(使用反斜杠换行)的反斜杠
AlignEscapedNewlines: Right
# 水平对齐二元和三元表达式的操作数
AlignOperands:   true
# 对齐连续的尾随的注释
AlignTrailingComments: true
# 允许函数声明的所有参数在放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 允许短的块放在同一行
AllowShortBlocksOnASingleLine: false
# 允许短的case标签放在同一行
AllowShortCaseLabelsOnASingleLine: false
# 允许短的函数放在同一行: None, InlineOnly(定义在类中), Empty(空函数), Inline(定义在类中,空函数), All
AllowShortFunctionsOnASingleLine: Empty
# 允许短的if语句保持在同一行
AllowShortIfStatementsOnASingleLine: false
# 允许短的循环保持在同一行
AllowShortLoopsOnASingleLine: false
# 总是在定义返回类型后换行(deprecated)
AlwaysBreakAfterDefinitionReturnType: None
# 总是在返回类型后换行: None, All, TopLevel(顶级函数,不包括在类中的函数),
#   AllDefinitions(所有的定义,不包括声明), TopLevelDefinitions(所有的顶级函数的定义)
AlwaysBreakAfterReturnType: None
# 总是在多行string字面量前换行
AlwaysBreakBeforeMultilineStrings: false
# 总是在template声明后换行
AlwaysBreakTemplateDeclarations: true
# false表示函数实参要么都在同一行,要么都各自一行
BinPackArguments: false
# false表示所有形参要么都在同一行,要么都各自一行
BinPackParameters: false

# 在大括号前换行: Attach(始终将大括号附加到周围的上下文), Linux(除函数、命名空间和类定义,与Attach类似),
#   Mozilla(除枚举、函数、记录定义,与Attach类似), Stroustrup(除函数定义、catch、else,与Attach类似),
#   Allman(总是在大括号前换行), GNU(总是在大括号前换行,并对于控制语句的大括号增加额外的缩进), WebKit(在函数前换行), Custom
#   注:这里认为语句块也属于函数
BreakBeforeBraces: Custom
# 大括号换行,只有当BreakBeforeBraces设置为Custom时才有效
BraceWrapping:   
  # class定义后面
  AfterClass: true
  # 控制语句后面
  AfterControlStatement: false
  # enum定义后面
  AfterEnum: true
  # 函数定义后面
  AfterFunction: true
  # 命名空间定义后面
  AfterNamespace: true
  # ObjC定义后面
  AfterObjCDeclaration: false
  # struct定义后面
  AfterStruct: true
  # union定义后面
  AfterUnion: true
  # extern 定义后面
  AfterExternBlock: true
  # catch之前
  BeforeCatch: false
  # else 之前
  BeforeElse: false
  # 缩进大括号
  IndentBraces: false

# 在二元运算符前换行: None(在操作符后换行), NonAssignment(在非赋值的操作符前换行), All(在操作符前换行)
BreakBeforeBinaryOperators: All

# 继承列表的逗号前换行
BreakBeforeInheritanceComma: true
# 继承列表换行
#BreakInheritanceList: BeforeColon
# 在三元运算符前换行
BreakBeforeTernaryOperators: true
# 在构造函数的初始化列表的逗号前换行
BreakConstructorInitializersBeforeComma: true
# 初始化列表前换行
BreakConstructorInitializers: BeforeComma
# Java注解后换行
BreakAfterJavaFieldAnnotations: false

BreakStringLiterals: true
# 每行字符的限制,0表示没有限制
ColumnLimit:     160
# 描述具有特殊意义的注释的正则表达式,它不应该被分割为多行或以其它方式改变
CommentPragmas:  '^ IWYU pragma:'
# 紧凑 命名空间
CompactNamespaces: false
# 构造函数的初始化列表要么都在同一行,要么都各自一行
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# 构造函数的初始化列表的缩进宽度
ConstructorInitializerIndentWidth: 4
# 延续的行的缩进宽度
ContinuationIndentWidth: 4
# 去除C++11的列表初始化的大括号{后和}前的空格
Cpp11BracedListStyle: false
# 继承最常用的指针和引用的对齐方式
DerivePointerAlignment: false
# 关闭格式化
DisableFormat:   false
# 自动检测函数的调用和定义是否被格式为每行一个参数(Experimental)
ExperimentalAutoDetectBinPacking: false
# 固定命名空间注释
FixNamespaceComments: true
# 需要被解读为foreach循环而不是函数调用的宏
ForEachMacros:   
  - foreach
  - Q_FOREACH
  - BOOST_FOREACH

IncludeBlocks:   Preserve
# 对#include进行排序,匹配了某正则表达式的#include拥有对应的优先级,匹配不到的则默认优先级为INT_MAX(优先级越小排序越靠前),
#   可以定义负数优先级从而保证某些#include永远在最前面
IncludeCategories: 
  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
    Priority:        2
  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
    Priority:        3
  - Regex:           'stdafx\.'  
    Priority:        1
  - Regex:           '.*'
    Priority:        1

IncludeIsMainRegex: '(Test)?$'
# 缩进case标签
IndentCaseLabels: false

IndentPPDirectives: None
# 缩进宽度
IndentWidth:     4
# 函数返回类型换行时,缩进函数声明或函数定义的函数名
IndentWrappedFunctionNames: true

JavaScriptQuotes: Leave

JavaScriptWrapImports: true
# 保留在块开始处的空行
KeepEmptyLinesAtTheStartOfBlocks: true
# 开始一个块的宏的正则表达式
MacroBlockBegin: ''
# 结束一个块的宏的正则表达式
MacroBlockEnd:   ''
# 连续空行的最大数量
MaxEmptyLinesToKeep: 1

# 命名空间的缩进: None, Inner(缩进嵌套的命名空间中的内容), All
NamespaceIndentation: All
ObjCBinPackProtocolList: Auto
# 使用ObjC块时缩进宽度
ObjCBlockIndentWidth: 4
# 在ObjC的@property后添加一个空格
ObjCSpaceAfterProperty: true
# 在ObjC的protocol列表前添加一个空格
ObjCSpaceBeforeProtocolList: true

PenaltyBreakAssignment: 2

PenaltyBreakBeforeFirstCallParameter: 19
# 在一个注释中引入换行的penalty
PenaltyBreakComment: 300
# 第一次在<<前换行的penalty
PenaltyBreakFirstLessLess: 120
# 在一个字符串字面量中引入换行的penalty
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
# 对于每个在行字符数限制之外的字符的penalty
PenaltyExcessCharacter: 1000000
# 将函数的返回类型放到它自己的行的penalty
PenaltyReturnTypeOnItsOwnLine: 60
# 指针和引用的对齐: Left, Right, Middle
PointerAlignment: Right

#RawStringFormats: 
#  - Delimiter:       pb
#    Language:        TextProto
#    BasedOnStyle:    google
# 允许重新排版注释
ReflowComments:  false
# 允许排序#include
SortIncludes:    true

SortUsingDeclarations: true
# 在C风格类型转换后添加空格
SpaceAfterCStyleCast: false
# 模板关键字后面添加空格
SpaceAfterTemplateKeyword: true
# 在赋值运算符之前添加空格
SpaceBeforeAssignmentOperators: true
# 开圆括号之前添加一个空格: Never, ControlStatements, Always
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
# 在空的圆括号中添加空格
SpaceInEmptyParentheses: false
# 在尾随的评论前添加的空格数(只适用于//)
SpacesBeforeTrailingComments: 1
# 在尖括号的<后和>前添加空格
SpacesInAngles:  false
# 在容器(ObjC和JavaScript的数组和字典等)字面量中添加空格
SpacesInContainerLiterals: true
# 在C风格类型转换的括号中添加空格
SpacesInCStyleCastParentheses: false
# 在圆括号的(后和)前添加空格
SpacesInParentheses: false
# 在方括号的[后和]前添加空格,lamda表达式和未指明大小的数组的声明不受影响
SpacesInSquareBrackets: false
# 标准: Cpp03, Cpp11, Auto
Standard:        Cpp11
# tab宽度
TabWidth:        4
# 使用tab字符: Never, ForIndentation, ForContinuationAndIndentation, Always
UseTab:          Never
...



================================================
FILE: .github/FUNDING.yml
================================================

# These are supported funding model platforms

github: barry-ran
patreon: # Replace with a single Patreon username
open_collective: QtScrcpy
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ["https://paypal.me/QtScrcpy", "https://gitee.com/Barryda/MyPictureBed/blob/master/QtScrcpy/payme.md"]

================================================
FILE: .github/workflows/macos.yml
================================================
name: MacOS
on: 
  push:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/macos.yml'
  pull_request:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/macos.yml'
jobs:
  build:
    name: Build
    # install-qt-action在arm上执行macdeployqt会报parse otool错误,所以在intel mac上执行:
    # 用qt6时在arm mac上编译arm和intel都没有问题
    # qt5+intel mac编译intel没问题
    # qt5+arm mac编译intel会报错
    # https://github.com/actions/runner-images?tab=readme-ov-file#available-images
    runs-on: macos-13
    strategy:
      matrix:
        qt-ver: [5.15.2, 6.5.3]        
        # 配置qt-ver的额外设置qt-arch-install,build-arch
        include:
          - qt-ver: 5.15.2
            qt-arch-install: clang_64
            build-arch: x64
          - qt-ver: 6.5.3
            qt-arch-install: arm64
            build-arch: arm64
    env:
      target-name: QtScrcpy
      qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
      plantform-des: mac
    steps:
      - name: Cache Qt
        id: cache-qt
        uses: actions/cache@v4
        with:
          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
          key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
      - name: Install Qt5
        if: startsWith(matrix.qt-ver, '5.')
        uses: jurplel/install-qt-action@v4.1.1
        with:
          version: ${{ matrix.qt-ver }}
          cached: ${{ steps.cache-qt.outputs.cache-hit }}
      - name: Install Qt6        
        if: startsWith(matrix.qt-ver, '6.')
        uses: jurplel/install-qt-action@v4.1.1
        with:
          version: ${{ matrix.qt-ver }}
          modules: qtmultimedia
          cached: ${{ steps.cache-qt.outputs.cache-hit }}
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
          submodules: 'true'
          ssh-key: ${{ secrets.BOT_SSH_KEY }}
      # 编译
      - name: Build MacOS
        env:
          ENV_QT_PATH: ${{ env.qt-install-path }}
        run: |
          python ci/generate-version.py
          ci/mac/build_for_mac.sh RelWithDebInfo ${{ matrix.build-arch }}
      # 获取ref最后一个/后的内容
      - name: Get the version
        shell: bash
        id: get-version
        # ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法
        run: echo ::set-output name=version::${GITHUB_REF##*/}
      # 打包
      - name: Package
        id: package
        env:
          ENV_QT_PATH: ${{ env.qt-install-path }}
          publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.build-arch }}-Qt${{matrix.qt-ver}}-${{ steps.get-version.outputs.version }}
        run: |
          ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }}
          ci/mac/package_for_mac.sh
          mv ci/build/QtScrcpy.app ci/build/${{ env.publish_name }}.app
          mv ci/build/QtScrcpy.dmg ci/build/${{ env.publish_name }}.dmg
          echo "::set-output name=package-name::${{ env.publish_name }}"
      - uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.package.outputs.package-name }}.zip
          path: ci/build/${{ steps.package.outputs.package-name }}.dmg
      # Upload to release
      - name: Upload Release
        if: startsWith(github.ref, 'refs/tags/')
        uses: svenstaro/upload-release-action@v1-release
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ci/build/${{ steps.package.outputs.package-name }}.dmg
          asset_name: ${{ steps.package.outputs.package-name }}.dmg
          tag: ${{ github.ref }}
          overwrite: true

================================================
FILE: .github/workflows/ubuntu.yml
================================================
name: Ubuntu
on: 
  push:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/ubuntu.yml'
      - 'ci/linux/**'
  pull_request:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/ubuntu.yml'
      - 'ci/linux/**'
jobs:
  build:
    name: Build
    runs-on: ubuntu-22.04
    container:
      image: ubuntu:20.04
      options: --privileged
    strategy:
      matrix:
        qt-ver: [5.15.2]
        qt-arch-install: [gcc_64]
        gcc-arch: [x64]
    env:
      target-name: QtScrcpy
      qt-install-path: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
      plantform-des: ubuntu
      DEBIAN_FRONTEND: noninteractive
    steps:
      - name: Install Git and basic dependencies
        run: |
          apt-get update
          apt-get install -y git ca-certificates sudo
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
          submodules: 'true'
          ssh-key: ${{ secrets.BOT_SSH_KEY }}
      - name: Install system dependencies
        run: |
          apt-get update
          apt-get install -y \
            build-essential \
            cmake \
            libglew-dev \
            libglfw3-dev \
            imagemagick \
            wget \
            patchelf \
            zip \
            libxcb1-dev \
            libxkbcommon-dev \
            libxkbcommon-x11-dev \
            libx11-dev \
            libx11-xcb-dev \
            libfontconfig1-dev \
            libfreetype6-dev \
            libxrender-dev \
            libxext-dev \
            gnupg \
            lsb-release \
            python3 \
            python3-pip \
            fuse \
            libasound2-dev
      - name: Setup FUSE
        run: |
          apt-get install -y fuse
          if [ ! -e /dev/fuse ]; then
            mknod /dev/fuse c 10 229 || true
            chmod 666 /dev/fuse || true
          fi
          export APPIMAGE_EXTRACT_AND_RUN=1
      - name: Install CMake 3.19+
        run: |
          apt-get install -y software-properties-common
          apt-get update
          apt-get install -y cmake=3.19.* || {
            wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | apt-key add -
            apt-add-repository 'deb https://apt.kitware.com/ubuntu/ focal main'
            apt-get update
            apt-get install -y cmake
          }
      - name: Cache Qt
        id: cache-qt
        uses: actions/cache@v4
        with:
          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
          key: ubuntu-20.04/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
      - name: Install Qt
        uses: jurplel/install-qt-action@v4.1.1
        with:
          version: ${{ matrix.qt-ver }}
          cache: ${{ steps.cache-qt.outputs.cache-hit }}
          setup-python: false
      - name: Build Release
        shell: bash
        env:
          ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
        run: |
          python3 ci/generate-version.py
          ci/linux/build_for_linux.sh "Release"
      - name: Get the version
        shell: bash
        id: get-version
        run: echo ::set-output name=version::${GITHUB_REF##*/}
      - name: Package AppImage
        shell: bash
        env:
          ENV_QT_PATH: ${{ github.workspace }}/Qt/${{ matrix.qt-ver }}
        run: |
          chmod +x ci/linux/package_appimage.sh
          ci/linux/package_appimage.sh "Release"
      - name: Upload AppImage Artifact
        uses: actions/upload-artifact@v4
        with:
          name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}-AppImage
          path: output/appimage/*.AppImage
          if-no-files-found: error
      - name: Prepare AppImage for Release
        if: startsWith(github.ref, 'refs/tags/')
        run: |
          APPIMAGE_FILE=$(find output/appimage -name "QtScrcpy-*.AppImage" -type f | head -n 1)
          if [ -z "$APPIMAGE_FILE" ] || [ ! -f "$APPIMAGE_FILE" ]; then
            echo "Error: AppImage file not found"
            exit 1
          fi
          FINAL_NAME="${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage"
          cp "$APPIMAGE_FILE" "$FINAL_NAME"
      - name: Upload AppImage to Releases
        if: startsWith(github.ref, 'refs/tags/')
        uses: svenstaro/upload-release-action@2.3.0
        with:
          file: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage
          asset_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.gcc-arch }}-${{ steps.get-version.outputs.version }}.AppImage
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          tag: ${{ github.ref }}
          overwrite: true

================================================
FILE: .github/workflows/windows.yml
================================================
name: Windows
# 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on
on: 
  push:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/windows.yml'
      - 'ci/win**'
  pull_request:
    paths:
      - 'QtScrcpy/**'
      - '!QtScrcpy/res/**'
      - '.github/workflows/windows.yml'
      - 'ci/win**'
jobs:
  build:
    name: Build
    # windows-latest目前是windows server 2022
    # windows server 2019安装的是vs2019,windows server 2016安装的是vs2017
    # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
    runs-on: windows-latest

    # 矩阵配置 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
    strategy:
      matrix:
        qt-ver: [5.15.2]
        qt-arch: [win64_msvc2019_64, win32_msvc2019]
        # 配置qt-arch的额外设置msvc-arch,qt-arch-install
        include:
          - qt-arch: win64_msvc2019_64
            msvc-arch: x64
            qt-arch-install: msvc2019_64
          - qt-arch: win32_msvc2019
            msvc-arch: x86
            qt-arch-install: msvc2019
    # job env,所有steps都可以访问
    # 不同级别env详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#env
    # 使用表达式语法${{}}访问上下文 https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions
    env:
      target-name: QtScrcpy
      vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
      vcinstall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC'
      qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
      plantform-des: win
    # 步骤
    steps:
      - name: Cache Qt
        id: cache-qt
        uses: actions/cache@v4
        with:
          path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
          key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch }}        
      # 安装Qt
      - name: Install Qt
        # 使用外部action。这个action专门用来安装Qt
        uses: jurplel/install-qt-action@v4.1.1
        with:
          # Version of Qt to install
          version: ${{ matrix.qt-ver }}
          # Target platform for build
          target: desktop
          # Architecture for Windows/Android
          arch: ${{ matrix.qt-arch }}
          cached: ${{ steps.cache-qt.outputs.cache-hit }}
      # 拉取代码
      - uses: actions/checkout@v2
        with:
          fetch-depth: 0
          submodules: 'true'
          ssh-key: ${{ secrets.BOT_SSH_KEY }}
      # 编译msvc
      - name: Build MSVC
        # shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
        shell: cmd
        env:
          ENV_VCVARSALL: ${{ env.vcvarsall-path }}
          ENV_QT_PATH: ${{ env.qt-install-path }}
        run: |
          call python ci\generate-version.py
          call "ci\win\build_for_win.bat" RelWithDebInfo ${{ matrix.msvc-arch }}
      # 获取ref最后一个/后的内容
      - name: Get the version
        shell: bash
        id: get-version
        # ${ GITHUB_REF/refs\/tags\// }是linux shell ${}的变量替换语法
        run: echo ::set-output name=version::${GITHUB_REF##*/}
      # tag 打包
      - name: Package
        id: package
        env:
          ENV_VCVARSALL: ${{ env.vcvarsall-path }}
          ENV_VCINSTALL: ${{ env.vcinstall-path }}
          ENV_QT_PATH: ${{ env.qt-install-path }}
          publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.msvc-arch }}-${{ steps.get-version.outputs.version }}
        run: |
          cmd.exe /c ci\win\publish_for_win.bat ${{ matrix.msvc-arch }} ..\build\${{ env.publish_name }}
          # 打包zip
          Compress-Archive -Path ci\build\${{ env.publish_name }} ci\build\${{ env.publish_name }}.zip
          echo "::set-output name=package-name::${{ env.publish_name }}"
      # 上传artifacts
      # https://help.github.com/en/actions/configuring-and-managing-workflows/persisting-workflow-data-using-artifacts
      - uses: actions/upload-artifact@v4
        with:
          name: ${{ steps.package.outputs.package-name }}.zip
          path: ci\build\${{ steps.package.outputs.package-name }}
      # Upload to release
      - name: Upload Release
        if: startsWith(github.ref, 'refs/tags/')
        uses: svenstaro/upload-release-action@v1-release
        with:
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          file: ci\build\${{ steps.package.outputs.package-name }}.zip
          asset_name: ${{ steps.package.outputs.package-name }}.zip
          tag: ${{ github.ref }}
          overwrite: true

================================================
FILE: .gitignore
================================================
/output
*.user
/QtScrcpy/*.user
/server/.gradle
/server/.idea
/server/build
/server/gradle/wrapper/gradle-wrapper.jar
/server/gradle/wrapper/gradle-wrapper.properties
/server/gradlew
/server/gradlew.bat
/server/local.properties
/build/
build-*
*.DS_Store
userdata.ini
Info_Mac.plist
/ci/build_temp

================================================
FILE: .gitmodules
================================================
[submodule "QtScrcpy/QtScrcpyCore"]
	path = QtScrcpy/QtScrcpyCore
	url = git@github.com:barry-ran/QtScrcpyCore.git


================================================
FILE: CMakeLists.txt
================================================
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
project(all)

add_subdirectory(QtScrcpy)


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "[]"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright (C) 2019 Rankun
   Copyright (C) 2019-2025 Rankun

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


================================================
FILE: QtScrcpy/CMakeLists.txt
================================================
# For VS2019 and Xcode 12+ support.
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)

#
# Global config
#

# QC is "Qt CMake"
# https://www.kdab.com/wp-content/uploads/stories/QTVTC20-Using-Modern-CMake-Kevin-Funk.pdf

# QC Custom config
set(QC_PROJECT_NAME "QtScrcpy")
# Read version numbers from file
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/appversion QC_FILE_VERSION)
set(QC_PROJECT_VERSION ${QC_FILE_VERSION})

# Project declare
project(${QC_PROJECT_NAME} VERSION ${QC_PROJECT_VERSION} LANGUAGES CXX)
message(STATUS "[${PROJECT_NAME}] Project ${PROJECT_NAME} ${PROJECT_VERSION}")

# QC define

# check arch
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
    set(QC_CPU_ARCH x64)
else()
    set(QC_CPU_ARCH x86)
endif()

# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    # mac default arch arm64
    if(NOT CMAKE_OSX_ARCHITECTURES)
        set(CMAKE_OSX_ARCHITECTURES arm64)
    endif()

    if (CMAKE_OSX_ARCHITECTURES MATCHES "arm64")
        set(QC_CPU_ARCH arm64)
    endif()
endif()

message(STATUS "[${PROJECT_NAME}] CPU_ARCH:${QC_CPU_ARCH}")

# CMake set
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# default RelWithDebInfo
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()
message(STATUS "[${PROJECT_NAME}] BUILD_TYPE:${CMAKE_BUILD_TYPE}")

# Log configuration
option(ENABLE_DETAILED_LOGS "Enable detailed log output with file and line info" OFF)
if(ENABLE_DETAILED_LOGS)
    message(STATUS "[${PROJECT_NAME}] Detailed logs enabled")
else()
    message(STATUS "[${PROJECT_NAME}] Simple logs enabled")
endif()

# Compiler set
message(STATUS "[${PROJECT_NAME}] C++ compiler ID is: ${CMAKE_CXX_COMPILER_ID}")
if (MSVC)
    # FFmpeg cannot be compiled natively by MSVC version < 12.0 (2013)
    if(MSVC_VERSION LESS 1800)
        message(FATAL_ERROR "[${PROJECT_NAME}] ERROR: MSVC version is older than 12.0 (2013).")
    endif()

    message(STATUS "[${PROJECT_NAME}] Set Warnings as error")
    # warning level 3 and all warnings as errors
    add_compile_options(/W3 /WX /wd4566)

    # avoid warning C4819
    #add_compile_options(-source-charset:utf-8)
    # /utf-8 will set source charset and execution charset to utf-8, so we don't need to set source-charset:utf-8
    add_compile_options(/utf-8)

    # ensure we use minimal "windows.h" lib without the crazy min max macros
    add_compile_definitions(NOMINMAX WIN32_LEAN_AND_MEAN)
    
    # disable SAFESEH - avoid "LNK2026: module unsafe"(Qt5.15&&vs2019)     
    add_link_options(/SAFESEH:NO)
endif()

if (NOT MSVC)
    message(STATUS "[${PROJECT_NAME}] Set warnings as error")
    # lots of warnings and all warnings as errors
    add_compile_options(-Wall -Wextra -pedantic -Werror)

    # disable some warning
    add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
endif()

#
# Qt
#

# Find Qt version
if (NOT QT_DESIRED_VERSION)
    find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
    message("   >>> Found Qt version: ${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
    set(QT_DESIRED_VERSION ${QT_VERSION_MAJOR})
endif()

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(qt_required_components Widgets Network Multimedia)

if (QT_DESIRED_VERSION MATCHES 6)
    # list(APPEND qt_required_components Core5Compat)
    list(APPEND qt_required_components OpenGL)
    list(APPEND qt_required_components OpenGLWidgets)
else()
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        list(APPEND qt_required_components X11Extras )
    endif()
endif()

find_package(Qt${QT_DESIRED_VERSION} REQUIRED COMPONENTS ${qt_required_components})

set(LINK_LIBS
    Qt${QT_DESIRED_VERSION}::Widgets
    Qt${QT_DESIRED_VERSION}::Network
    Qt${QT_DESIRED_VERSION}::Multimedia
)

if (QT_DESIRED_VERSION MATCHES 6)
    # list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::Core5Compat)
    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::GuiPrivate)
    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGL)
    list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::OpenGLWidgets)
else()
    if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
        list(APPEND LINK_LIBS Qt${QT_DESIRED_VERSION}::X11Extras)
    endif()
endif()

message(STATUS "[${PROJECT_NAME}] Qt version is: ${QT_DESIRED_VERSION}")

#
# Sources
#

# fontawesome
set(QC_FONTAWESOME_SOURCES
    fontawesome/iconhelper.h
    fontawesome/iconhelper.cpp
)
source_group(fontawesome FILES ${QC_FONTAWESOME_SOURCES})

# uibase
set(QC_UIBASE_SOURCES
    uibase/keepratiowidget.h
    uibase/keepratiowidget.cpp
    uibase/magneticwidget.h
    uibase/magneticwidget.cpp
)
source_group(uibase FILES ${QC_UIBASE_SOURCES})

# audio
set(QC_AUDIO_SOURCES
    audio/audiooutput.h
    audio/audiooutput.cpp
)
source_group(audio FILES ${QC_AUDIO_SOURCES})

# ui
set(QC_UI_SOURCES
    ui/toolform.h
    ui/toolform.cpp
    ui/toolform.ui
    ui/videoform.h
    ui/videoform.cpp
    ui/videoform.ui
    ui/dialog.cpp
    ui/dialog.h
    ui/dialog.ui
    render/qyuvopenglwidget.h
    render/qyuvopenglwidget.cpp
)
source_group(ui FILES ${QC_UI_SOURCES})

# group controller
set(QC_GROUP_CONTROLLER
    groupcontroller/groupcontroller.h
    groupcontroller/groupcontroller.cpp
)
source_group(groupcontroller FILES ${QC_GROUP_CONTROLLER})

# util
set(QC_UTIL_SOURCES
    util/config.h
    util/config.cpp
    util/mousetap/mousetap.h
    util/mousetap/mousetap.cpp
)
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} 
        util/mousetap/winmousetap.h
        util/mousetap/winmousetap.cpp
        util/winutils.h
        util/winutils.cpp
    )
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} 
        util/mousetap/xmousetap.h
        util/mousetap/xmousetap.cpp
    )
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    set(QC_UTIL_SOURCES ${QC_UTIL_SOURCES} 
        util/mousetap/cocoamousetap.h
        util/mousetap/cocoamousetap.mm
        util/path.h
        util/path.mm
    )
endif()
source_group(util FILES ${QC_UTIL_SOURCES})

# qrc
set(QC_QRC_SOURCES "res/res.qrc")

# main
set(QC_MAIN_SOURCES
    main.cpp
    ${QC_QRC_SOURCES}
)

# plantform file
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    # Define VERSION macros for .rc file
    add_compile_definitions(
        VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
        VERSION_MINOR=${PROJECT_VERSION_MINOR}
        VERSION_PATCH=${PROJECT_VERSION_PATCH}
        VERSION_RC_STR="${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}"
    )
    set(QC_PLANTFORM_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.rc"
    )
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    # Step 1. add icns to source file, for MACOSX_PACKAGE_LOCATION copy
    set(QC_PLANTFORM_SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns"
    )
endif()

# 翻译相关(使用shell脚本替代cmake处理翻译)
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/res/i18n)

# all sources
set(QC_PROJECT_SOURCES
    ${QC_FONTAWESOME_SOURCES}
    ${QC_UIBASE_SOURCES}
    ${QC_UI_SOURCES}
    ${QC_UTIL_SOURCES}
    ${QC_MAIN_SOURCES}
    ${QC_GROUP_CONTROLLER}
    ${QC_PLANTFORM_SOURCES}
    ${QC_AUDIO_SOURCES}
)

if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    set(QC_RUNTIME_TYPE MACOSX_BUNDLE)
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    set(QC_RUNTIME_TYPE WIN32)
endif()


add_executable(${PROJECT_NAME} ${QC_RUNTIME_TYPE} ${QC_PROJECT_SOURCES})

# Log compile definitions
if(ENABLE_DETAILED_LOGS)
    target_compile_definitions(${PROJECT_NAME} PRIVATE ENABLE_DETAILED_LOGS)
endif()

#
# Internal include path (todo: remove this, use absolute path include)
#

target_include_directories(${PROJECT_NAME} PRIVATE fontawesome)
target_include_directories(${PROJECT_NAME} PRIVATE util)
target_include_directories(${PROJECT_NAME} PRIVATE uibase)
target_include_directories(${PROJECT_NAME} PRIVATE ui)
target_include_directories(${PROJECT_NAME} PRIVATE render)

# output dir
# https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
get_property(QC_IS_MUTIL_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
message(STATUS "multi config:" QC_IS_MUTIL_CONFIG)

# $<0:> 使用生成器表达式为每个config设置RUNTIME_OUTPUT_DIRECTORY,这样multi config就不会自动追加CMAKE_BUILD_TYPE子目录了
# 1. multi config介绍 https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html
# 2. multi config在不用表达式生成器时自动追加子目录说明 https://cmake.org/cmake/help/latest/prop_tgt/RUNTIME_OUTPUT_DIRECTORY.html
# 3. 使用表达式生成器禁止multi config自动追加子目录解决方案 https://stackoverflow.com/questions/7747857/in-cmake-how-do-i-work-around-the-debug-and-release-directories-visual-studio-2
set_target_properties(${PROJECT_NAME} PROPERTIES
    RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/$<0:>"
)

#
# plantform deps
#

# windows
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
    set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.bat" "${QSC_BIN_OUTPUT_PATH}"
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
    )
endif()

# MacOS
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    # qt6 need 10.15 or later
    set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15")

    # copy bundle file
    get_target_property(MACOS_BUNDLE_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
    set(MACOS_BUNDLE_PATH ${MACOS_BUNDLE_PATH}/${PROJECT_NAME}.app/Contents)

    set(QSC_DEPLOY_PATH ${MACOS_BUNDLE_PATH})

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        # config file copy to Contents/MacOS/config
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini"
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${MACOS_BUNDLE_PATH}/MacOS"
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${MACOS_BUNDLE_PATH}/MacOS"
    )

    # Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources
    set_source_files_properties(
        ${CMAKE_CURRENT_SOURCE_DIR}/res/${PROJECT_NAME}.icns
        PROPERTIES MACOSX_PACKAGE_LOCATION Resources
    )

    # use MACOSX_BUNDLE_INFO_PLIST custom plist, not use MACOSX_BUNDLE_BUNDLE_NAME etc..
    set(INFO_PLIST_TEMPLATE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist.in")
    set(INFO_PLIST_FILE "${CMAKE_CURRENT_SOURCE_DIR}/res/Info_Mac.plist")
    file(READ "${INFO_PLIST_TEMPLATE_FILE}" plist_contents)
    string(REPLACE "\${BUNDLE_VERSION}" "${PROJECT_VERSION}" plist_contents ${plist_contents})
    file(WRITE ${INFO_PLIST_FILE} ${plist_contents})
    set_target_properties(${PROJECT_NAME} PROPERTIES
        MACOSX_BUNDLE_INFO_PLIST "${INFO_PLIST_FILE}"
        # "" disable code sign
        XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY ""
    )

    # mac framework
    target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AppKit")
endif()

# Linux
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
    set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})

    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.sh" "${QSC_BIN_OUTPUT_PATH}"
        COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/sndcpy/sndcpy.apk" "${QSC_BIN_OUTPUT_PATH}"
    )

    set(THREADS_PREFER_PTHREAD_FLAG ON)
    find_package(Threads REQUIRED)

    target_link_libraries(${PROJECT_NAME} PRIVATE
        # xcb https://doc.qt.io/qt-5/linux-requirements.html
        xcb
        # pthread
        Threads::Threads
    )

    # linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
endif()

#
# common deps
#

add_subdirectory(QtScrcpyCore)

# Qt
target_link_libraries(${PROJECT_NAME} PRIVATE
    ${LINK_LIBS}
    QtScrcpyCore
)


================================================
FILE: QtScrcpy/appversion
================================================
0.0.0


================================================
FILE: QtScrcpy/audio/audiooutput.cpp
================================================
#include <QAudioOutput>
#include <QCoreApplication>
#include <QElapsedTimer>
#include <QHostAddress>
#include <QTcpSocket>
#include <QTime>

#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
#include <QAudioSink>
#include <QAudioDevice>
#include <QMediaDevices>
#endif

#include "audiooutput.h"

AudioOutput::AudioOutput(QObject *parent)
    : QObject(parent)
{
    m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    m_audioOutput = nullptr;
#else
    m_audioSink = nullptr;
#endif
    connect(&m_sndcpy, &QProcess::readyReadStandardOutput, this, [this]() {
        qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardOutput());
    });
    connect(&m_sndcpy, &QProcess::readyReadStandardError, this, [this]() {
        qInfo() << QString("AudioOutput::") << QString(m_sndcpy.readAllStandardError());
    });
}

AudioOutput::~AudioOutput()
{
    if (QProcess::NotRunning != m_sndcpy.state()) {
        m_sndcpy.kill();
    }
    stop();
}

bool AudioOutput::start(const QString& serial, int port)
{
    if (m_running) {
        stop();
    }

    QElapsedTimer timeConsumeCount;
    timeConsumeCount.start();
    bool ret = runSndcpyProcess(serial, port);
    qInfo() << "AudioOutput::run sndcpy cost:" << timeConsumeCount.elapsed() << "milliseconds";
    if (!ret) {
        return ret;
    }

    startAudioOutput();
    startRecvData(port);

    m_running = true;
    return true;
}

void AudioOutput::stop()
{
    if (!m_running) {
        return;
    }
    m_running = false;

    stopRecvData();
    stopAudioOutput();
}

void AudioOutput::installonly(const QString &serial, int port)
{
    runSndcpyProcess(serial, port, false);
}

bool AudioOutput::runSndcpyProcess(const QString &serial, int port, bool wait)
{
    if (QProcess::NotRunning != m_sndcpy.state()) {
        m_sndcpy.kill();
    }

#ifdef Q_OS_WIN32
    QStringList params{serial, QString::number(port)};
    m_sndcpy.start("sndcpy.bat", params);
#else
    QStringList params{"sndcpy.sh", serial, QString::number(port)};
    m_sndcpy.setWorkingDirectory(QCoreApplication::applicationDirPath());
    m_sndcpy.start("bash", params);
#endif

    if (!wait) {
        return true;
    }

    if (!m_sndcpy.waitForStarted()) {
        qWarning() << "AudioOutput::start sndcpy process failed";
        return false;
    }
    if (!m_sndcpy.waitForFinished()) {
        qWarning() << "AudioOutput::sndcpy process crashed";
        return false;
    }

    return true;
}

void AudioOutput::startAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    if (m_audioOutput) {
        return;
    }

    QAudioFormat format;
    format.setSampleRate(48000);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");
    format.setByteOrder(QAudioFormat::LittleEndian);
    format.setSampleType(QAudioFormat::SignedInt);
    QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());

    if (!info.isFormatSupported(format)) {
        qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
        return;
    }

    m_audioOutput = new QAudioOutput(format, this);
    connect(m_audioOutput, &QAudioOutput::stateChanged, this, [](QAudio::State state) {
        qInfo() << "AudioOutput::audio state changed:" << state;
    });
    m_audioOutput->setBufferSize(48000*2*15/1000 * 20);
    m_outputDevice = m_audioOutput->start();
#else
    if (m_audioSink) {
        return;
    }

    QAudioFormat format;
    format.setSampleRate(48000);
    format.setChannelCount(2);
    format.setSampleFormat(QAudioFormat::Int16);
    QAudioDevice defaultDevice = QMediaDevices::defaultAudioOutput();
    if (!defaultDevice.isFormatSupported(format)) {
        qWarning() << "AudioOutput::audio format not supported, cannot play audio.";
        return;
    }
    m_audioSink = new QAudioSink(defaultDevice, format, this);
    m_outputDevice = m_audioSink->start();
    if (!m_outputDevice) {
        qWarning() << "AudioOutput::audio output device not available, cannot play audio.";
        delete m_audioSink;
        m_audioSink = nullptr;
        return;
    }
#endif
}

void AudioOutput::stopAudioOutput()
{
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    if (m_audioOutput) {
        m_audioOutput->stop();
        delete m_audioOutput;
        m_audioOutput = nullptr;
    }
#else
    if (m_audioSink) {
        m_audioSink->stop();
        delete m_audioSink;
        m_audioSink = nullptr;
    }
#endif
    m_outputDevice = nullptr;
}

void AudioOutput::startRecvData(int port)
{
    if (m_workerThread.isRunning()) {
        stopRecvData();
    }

    auto audioSocket = new QTcpSocket();
    audioSocket->moveToThread(&m_workerThread);
    connect(&m_workerThread, &QThread::finished, audioSocket, &QObject::deleteLater);

    connect(this, &AudioOutput::connectTo, audioSocket, [audioSocket](int port) {
        audioSocket->connectToHost(QHostAddress::LocalHost, port);
        if (!audioSocket->waitForConnected(500)) {
            qWarning("AudioOutput::audio socket connect failed");
            return;
        }
        qInfo("AudioOutput::audio socket connect success");
    });
    connect(audioSocket, &QIODevice::readyRead, audioSocket, [this, audioSocket]() {
        qint64 recv = audioSocket->bytesAvailable();
        //qDebug() << "AudioOutput::recv data:" << recv;

        if (!m_outputDevice) {
            return;
        }
        if (m_buffer.capacity() < recv) {
            m_buffer.reserve(recv);
        }

        qint64 count = audioSocket->read(m_buffer.data(), recv);
        m_outputDevice->write(m_buffer.data(), count);
    });
    connect(audioSocket, &QTcpSocket::stateChanged, audioSocket, [](QAbstractSocket::SocketState state) {
        qInfo() << "AudioOutput::audio socket state changed:" << state;

    });
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    connect(audioSocket, &QTcpSocket::errorOccurred, audioSocket, [](QAbstractSocket::SocketError error) {
        qInfo() << "AudioOutput::audio socket error occurred:" << error;
    });
#else
    connect(audioSocket, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), audioSocket, [](QAbstractSocket::SocketError error) {
        qInfo() << "AudioOutput::audio socket error occurred:" << error;
    });
#endif

    m_workerThread.start();
    emit connectTo(port);
}

void AudioOutput::stopRecvData()
{
    if (!m_workerThread.isRunning()) {
        return;
    }

    m_workerThread.quit();
    m_workerThread.wait();
}


================================================
FILE: QtScrcpy/audio/audiooutput.h
================================================
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H

#include <QThread>
#include <QProcess>
#include <QPointer>
#include <QVector>

class QAudioSink;
class QAudioOutput;
class QIODevice;
class AudioOutput : public QObject
{
    Q_OBJECT
public:
    explicit AudioOutput(QObject *parent = nullptr);
    ~AudioOutput();

    bool start(const QString& serial, int port);
    void stop();
    void installonly(const QString& serial, int port);

private:
    bool runSndcpyProcess(const QString& serial, int port, bool wait = true);
    void startAudioOutput();
    void stopAudioOutput();
    void startRecvData(int port);
    void stopRecvData();

signals:
    void connectTo(int port);

private:
    QPointer<QIODevice> m_outputDevice;
    QThread m_workerThread;
    QProcess m_sndcpy;
    QVector<char> m_buffer;
    bool m_running = false;
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    QAudioOutput* m_audioOutput = nullptr;
#else
    QAudioSink *m_audioSink = nullptr;
#endif
};

#endif // AUDIOOUTPUT_H


================================================
FILE: QtScrcpy/clang-format-all.sh
================================================
#!/bin/bash
#
# clang-format-all: a tool to run clang-format on an entire project
# Copyright (C) 2016 Evan Klitzke <evan@eklitzke.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

function usage {
    echo "Usage: $0 DIR..."
    exit 1
}

if [ $# -eq 0 ]; then
    usage
fi

# Variable that will hold the name of the clang-format command
FMT=""

# Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent
# that the version number be part of the command. We prefer clang-format if
# that's present, otherwise we work backwards from highest version to lowest
# version.
for clangfmt in clang-format{,-{4,3}.{9,8,7,6,5,4,3,2,1,0}}; do
    if which "$clangfmt" &>/dev/null; then
        FMT="$clangfmt"
        break
    fi
done

# Check if we found a working clang-format
if [ -z "$FMT" ]; then
    echo "failed to find clang-format"
    exit 1
fi

# Check all of the arguments first to make sure they're all directories
for dir in "$@"; do
    if [ ! -d "${dir}" ]; then
        echo "${dir} is not a directory"
        usage
    fi
done

# Find a dominating file, starting from a given directory and going up.
find-dominating-file() {
    if [ -r "$1"/"$2" ]; then
        return 0
    fi
    if [ "$1" = "/" ]; then
        return 1
    fi
    find-dominating-file "$(realpath "$1"/..)" "$2"
    return $?
}

# Run clang-format -i on all of the things
for dir in "$@"; do
    pushd "${dir}" &>/dev/null
    if ! find-dominating-file . .clang-format; then
        echo "Failed to find dominating .clang-format starting at $PWD"
        continue
    fi
    find . \
         \( -name '*.c' \
         -o -name '*.cc' \
         -o -name '*.cpp' \
         -o -name '*.h' \
         -o -name '*.hh' \
         -o -name '*.hpp' \) \
         -exec "${FMT}" -i '{}' \;
    popd &>/dev/null
done


================================================
FILE: QtScrcpy/fontawesome/iconhelper.cpp
================================================
#include "iconhelper.h"

IconHelper *IconHelper::_instance = 0;
IconHelper::IconHelper(QObject *) : QObject(qApp)
{
    int fontId = QFontDatabase::addApplicationFont(":/font/fontawesome-webfont.ttf");
    QString fontName = QFontDatabase::applicationFontFamilies(fontId).at(0);
    iconFont = QFont(fontName);
}

void IconHelper::SetIcon(QLabel *lab, QChar c, int size)
{
    iconFont.setPointSize(size);
    lab->setFont(iconFont);
    lab->setText(c);
}

void IconHelper::SetIcon(QPushButton *btn, QChar c, int size)
{
    iconFont.setPointSize(size);
    btn->setFont(iconFont);
    btn->setText(c);
}


================================================
FILE: QtScrcpy/fontawesome/iconhelper.h
================================================
#ifndef ICONHELPER_H
#define ICONHELPER_H

#include <QApplication>
#include <QFont>
#include <QFontDatabase>
#include <QLabel>
#include <QMutex>
#include <QObject>
#include <QPushButton>

class IconHelper : public QObject
{
private:
    explicit IconHelper(QObject *parent = 0);
    QFont iconFont;
    static IconHelper *_instance;

public:
    static IconHelper *Instance()
    {
        static QMutex mutex;
        if (!_instance) {
            QMutexLocker locker(&mutex);
            if (!_instance) {
                _instance = new IconHelper;
            }
        }
        return _instance;
    }

    void SetIcon(QLabel *lab, QChar c, int size = 10);
    void SetIcon(QPushButton *btn, QChar c, int size = 10);
};

#endif // ICONHELPER_H


================================================
FILE: QtScrcpy/groupcontroller/groupcontroller.cpp
================================================
#include <QPointer>

#include "groupcontroller.h"
#include "videoform.h"

GroupController::GroupController(QObject *parent) : QObject(parent)
{

}

bool GroupController::isHost(const QString &serial)
{
    auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
    if (!data) {
        return true;
    }

    return static_cast<VideoForm*>(data)->isHost();
}

QSize GroupController::getFrameSize(const QString &serial)
{
    auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
    if (!data) {
        return QSize();
    }

    return static_cast<VideoForm*>(data)->frameSize();
}

GroupController &GroupController::instance()
{
    static GroupController gc;
    return gc;
}

void GroupController::updateDeviceState(const QString &serial)
{
    if (!m_devices.contains(serial)) {
        return;
    }

    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
    if (!device) {
        return;
    }

    if (isHost(serial)) {
        device->registerDeviceObserver(this);
    } else {
        device->deRegisterDeviceObserver(this);
    }
}

void GroupController::addDevice(const QString &serial)
{
    if (m_devices.contains(serial)) {
        return;
    }

    m_devices.append(serial);
}

void GroupController::removeDevice(const QString &serial)
{
    if (!m_devices.contains(serial)) {
        return;
    }

    m_devices.removeOne(serial);

    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
    if (!device) {
        return;
    }

    if (isHost(serial)) {
        device->deRegisterDeviceObserver(this);
    }
}

void GroupController::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
{
    Q_UNUSED(frameSize);
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->mouseEvent(from, getFrameSize(serial), showSize);
    }
}

void GroupController::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
{
    Q_UNUSED(frameSize);
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->wheelEvent(from, getFrameSize(serial), showSize);
    }
}

void GroupController::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
{
    Q_UNUSED(frameSize);
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->keyEvent(from, getFrameSize(serial), showSize);
    }
}

void GroupController::postGoBack()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postGoBack();
    }
}

void GroupController::postGoHome()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postGoHome();
    }
}

void GroupController::postGoMenu()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postGoMenu();
    }
}

void GroupController::postAppSwitch()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postAppSwitch();
    }
}

void GroupController::postPower()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postPower();
    }
}

void GroupController::postVolumeUp()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postVolumeUp();
    }
}

void GroupController::postVolumeDown()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postVolumeDown();
    }
}

void GroupController::postCopy()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postCopy();
    }
}

void GroupController::postCut()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postCut();
    }
}

void GroupController::setDisplayPower(bool on)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->setDisplayPower(on);
    }
}

void GroupController::expandNotificationPanel()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->expandNotificationPanel();
    }
}

void GroupController::collapsePanel()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->collapsePanel();
    }
}

void GroupController::postBackOrScreenOn(bool down)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postBackOrScreenOn(down);
    }
}

void GroupController::postTextInput(QString &text)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->postTextInput(text);
    }
}

void GroupController::requestDeviceClipboard()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->requestDeviceClipboard();
    }
}

void GroupController::setDeviceClipboard(bool pause)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->setDeviceClipboard(pause);
    }
}

void GroupController::clipboardPaste()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->clipboardPaste();
    }
}

void GroupController::pushFileRequest(const QString &file, const QString &devicePath)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->pushFileRequest(file, devicePath);
    }
}

void GroupController::installApkRequest(const QString &apkFile)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->installApkRequest(apkFile);
    }
}

void GroupController::screenshot()
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->screenshot();
    }
}

void GroupController::showTouch(bool show)
{
    for (const auto& serial : m_devices) {
        if (true == isHost(serial)) {
            continue;
        }
        auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
        if (!device) {
            continue;
        }

        device->showTouch(show);
    }
}


================================================
FILE: QtScrcpy/groupcontroller/groupcontroller.h
================================================
#ifndef GROUPCONTROLLER_H
#define GROUPCONTROLLER_H

#include <QObject>
#include <QVector>

#include "QtScrcpyCore.h"

class GroupController : public QObject, public qsc::DeviceObserver
{
    Q_OBJECT
public:
    static GroupController& instance();

    void updateDeviceState(const QString& serial);
    void addDevice(const QString& serial);
    void removeDevice(const QString& serial);

private:
    // DeviceObserver
    void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) override;
    void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) override;
    void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) override;

    void postGoBack() override;
    void postGoHome() override;
    void postGoMenu() override;
    void postAppSwitch() override;
    void postPower() override;
    void postVolumeUp() override;
    void postVolumeDown() override;
    void postCopy() override;
    void postCut() override;
    void setDisplayPower(bool on) override;
    void expandNotificationPanel() override;
    void collapsePanel() override;
    void postBackOrScreenOn(bool down) override;
    void postTextInput(QString &text) override;
    void requestDeviceClipboard() override;
    void setDeviceClipboard(bool pause = true) override;
    void clipboardPaste() override;
    void pushFileRequest(const QString &file, const QString &devicePath = "") override;
    void installApkRequest(const QString &apkFile) override;
    void screenshot() override;
    void showTouch(bool show) override;

private:
    explicit GroupController(QObject *parent = nullptr);
    bool isHost(const QString& serial);
    QSize getFrameSize(const QString& serial);

private:
    QVector<QString> m_devices;
};

#endif // GROUPCONTROLLER_H


================================================
FILE: QtScrcpy/main.cpp
================================================
#include <QApplication>
#include <QDebug>
#include <QFile>
#ifdef Q_OS_LINUX
#include <QFileInfo>
#include <QIcon>
#endif
#include <QSurfaceFormat>
#include <QTcpServer>
#include <QTcpSocket>
#include <QTranslator>
#include <QDateTime>

#include "config.h"
#include "dialog.h"
#include "mousetap/mousetap.h"

static Dialog *g_mainDlg = Q_NULLPTR;
static QtMessageHandler g_oldMessageHandler = Q_NULLPTR;
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void installTranslator();

static QtMsgType g_msgType = QtInfoMsg;
QtMsgType covertLogLevel(const QString &logLevel);

int main(int argc, char *argv[])
{
    // set env
#ifdef Q_OS_WIN32
    qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/win/adb.exe");
    qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
    qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
    qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
#endif

#ifdef Q_OS_OSX
    qputenv("QTSCRCPY_ADB_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/mac/adb");
    qputenv("QTSCRCPY_SERVER_PATH", "../../../../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
    qputenv("QTSCRCPY_KEYMAP_PATH", "../../../../../../keymap");
    qputenv("QTSCRCPY_CONFIG_PATH", "../../../../../../config");
#endif

#ifdef Q_OS_LINUX
    // Only set environment variables if they are not already set (e.g., by AppImage AppRun)
    if (qgetenv("QTSCRCPY_ADB_PATH").isEmpty()) {
        qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
    }
    if (qgetenv("QTSCRCPY_SERVER_PATH").isEmpty()) {
        qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
    }
    if (qgetenv("QTSCRCPY_KEYMAP_PATH").isEmpty()) {
        qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
    }
    if (qgetenv("QTSCRCPY_CONFIG_PATH").isEmpty()) {
        qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
    }
#endif

    g_msgType = covertLogLevel(Config::getInstance().getLogLevel());

    // set on QApplication before
    // bug: config path is error on mac
    int opengl = Config::getInstance().getDesktopOpenGL();
    if (0 == opengl) {
        QApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
    } else if (1 == opengl) {
        QApplication::setAttribute(Qt::AA_UseOpenGLES);
    } else if (2 == opengl) {
        QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
    }

#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

#if (QT_VERSION >= QT_VERSION_CHECK(5,14,0))
    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
#endif
#endif

    QSurfaceFormat varFormat = QSurfaceFormat::defaultFormat();
    varFormat.setVersion(2, 0);
    varFormat.setProfile(QSurfaceFormat::NoProfile);
    /*
    varFormat.setSamples(4);
    varFormat.setAlphaBufferSize(8);
    varFormat.setBlueBufferSize(8);
    varFormat.setRedBufferSize(8);
    varFormat.setGreenBufferSize(8);
    varFormat.setDepthBufferSize(24);
    */
    QSurfaceFormat::setDefaultFormat(varFormat);

    g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);
    QApplication a(argc, argv);

    // Set application icon for Linux (taskbar icon)
#ifdef Q_OS_LINUX
    // Load icon from Qt resource (logo.png is included in res.qrc)
    QIcon appIcon(":/image/tray/logo.png");
    if (!appIcon.isNull()) {
        a.setWindowIcon(appIcon);
    }
#endif

    // windows下通过qmake VERSION变量或者rc设置版本号和应用名称后,这里可以直接拿到
    // mac下拿到的是CFBundleVersion的值
    qDebug() << a.applicationVersion();
    qDebug() << a.applicationName();

    //update version
    QStringList versionList = QCoreApplication::applicationVersion().split(".");
    if (versionList.size() >= 3) {
        QString version = versionList[0] + "." + versionList[1] + "." + versionList[2];
        a.setApplicationVersion(version);
    }

    installTranslator();
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
    MouseTap::getInstance()->initMouseEventTap();
#endif

    // load style sheet
    QFile file(":/qss/psblack.css");
    if (file.open(QFile::ReadOnly)) {
        QString qss = QLatin1String(file.readAll());
        QString paletteColor = qss.mid(20, 7);
        qApp->setPalette(QPalette(QColor(paletteColor)));
        qApp->setStyleSheet(qss);
        file.close();
    }

    qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());

    g_mainDlg = new Dialog {};
    g_mainDlg->show();

    qInfo() << QObject::tr("This software is completely open source and free. Use it at your own risk. You can download it at the "
            "following address:");
    qInfo() << QString("QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion());

    qInfo() << QObject::tr("If you need more professional batch control mirror software, you can try the following software:");
    qInfo() << QString(QObject::tr("QuickMirror") + " <https://lrbnfell4p.feishu.cn/drive/folder/KviYfz5uFlpUT8dXgdjccmfUnse>");

    qInfo() << QObject::tr("If you need more professional game keymap mirror software, you can try the following software:");
    qInfo() << QString(QObject::tr("QuickAssistant") + " <https://lrbnfell4p.feishu.cn/drive/folder/Hqckfxj5el1Wjpd9uezcX71lnBh>");

    qInfo() << QObject::tr("If you need more professional PC remote software, you can try the following software:");
    qInfo() << QString(QObject::tr("QuickDesk") + " <https://github.com/barry-ran/QuickDesk>");

    qInfo() << QObject::tr("You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1>");

    int ret = a.exec();
    delete g_mainDlg;

#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
    MouseTap::getInstance()->quitMouseEventTap();
#endif
    return ret;
}

void installTranslator()
{
    static QTranslator translator;
    QLocale locale;
    QLocale::Language language = locale.language();

    if (Config::getInstance().getLanguage() == "zh_CN") {
        language = QLocale::Chinese;
    } else if (Config::getInstance().getLanguage() == "en_US") {
        language = QLocale::English;
    } else if (Config::getInstance().getLanguage() == "ja_JP") {
        language = QLocale::Japanese;
    }

    QString languagePath = ":/i18n/";
    switch (language) {
    case QLocale::Chinese:
        languagePath += "zh_CN.qm";
        break;
    case QLocale::Japanese:
        languagePath += "ja_JP.qm";
        break;
    case QLocale::English:
    default:
        languagePath += "en_US.qm";
        break;
    }

    auto loaded = translator.load(languagePath);
    if (!loaded) {
        qWarning() << "Failed to load translation file:" << languagePath;
    }
    qApp->installTranslator(&translator);
}

QtMsgType covertLogLevel(const QString &logLevel)
{
    if ("debug" == logLevel) {
        return QtDebugMsg;
    }

    if ("info" == logLevel) {
        return QtInfoMsg;
    }

    if ("warn" == logLevel) {
        return QtWarningMsg;
    }

    if ("error" == logLevel) {
        return QtCriticalMsg;
    }

#ifdef QT_NO_DEBUG
    return QtInfoMsg;
#else
    return QtDebugMsg;
#endif
}

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QString outputMsg;
    
#ifdef ENABLE_DETAILED_LOGS
    QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
    
    if (context.file && context.line > 0) {
        QString fileName = QString::fromUtf8(context.file);

        int lastSlash = fileName.lastIndexOf('/');
        if (lastSlash >= 0) {
            fileName = fileName.mid(lastSlash + 1);
        }
        lastSlash = fileName.lastIndexOf('\\');
        if (lastSlash >= 0) {
            fileName = fileName.mid(lastSlash + 1);
        }
        
        outputMsg = QString("[ %1 %2: %3 ] %4").arg(timestamp).arg(fileName).arg(context.line).arg(msg);
    } else {
        outputMsg = QString("[%1] %2").arg(timestamp).arg(msg);
    }

    switch (type) {
    case QtDebugMsg:
        outputMsg.prepend("[debug] ");
        break;
    case QtInfoMsg:
        outputMsg.prepend("[info] ");
        break;
    case QtWarningMsg:
        outputMsg.prepend("[warring] ");
        break;
    case QtCriticalMsg:
        outputMsg.prepend("[critical] ");
        break;
    case QtFatalMsg:
        outputMsg.prepend("[fatal] ");
        break;
    }

    fprintf(stderr, "%s\n", outputMsg.toUtf8().constData());
#else
    outputMsg = msg;
    if (g_oldMessageHandler) {
        g_oldMessageHandler(type, context, outputMsg);
    }
#endif

    // Is Qt log level higher than warning?
    float fLogLevel = g_msgType;
    if (QtInfoMsg == g_msgType) {
        fLogLevel = QtDebugMsg + 0.5f;
    }
    float fLogLevel2 = type;
    if (QtInfoMsg == type) {
        fLogLevel2 = QtDebugMsg + 0.5f;
    }

    if (fLogLevel <= fLogLevel2) {
        if (g_mainDlg && g_mainDlg->isVisible() && !g_mainDlg->filterLog(outputMsg)) {
            g_mainDlg->outLog(outputMsg);
        }
    }

    if (QtFatalMsg == type) {
        //abort();
    }
}


================================================
FILE: QtScrcpy/render/qyuvopenglwidget.cpp
================================================
#include <QCoreApplication>
#include <QOpenGLTexture>
#include <QSurfaceFormat>

#include "qyuvopenglwidget.h"

// 存储顶点坐标和纹理坐标
// 存在一起缓存在vbo
// 使用glVertexAttribPointer指定访问方式即可
static const GLfloat coordinate[] = {
    // 顶点坐标,存储4个xyz坐标
    // 坐标范围为[-1,1],中心点为 0,0
    // 二维图像z始终为0
    // GL_TRIANGLE_STRIP的绘制方式:
    // 使用前3个坐标绘制一个三角形,使用后三个坐标绘制一个三角形,正好为一个矩形
    // x     y     z
    -1.0f,
    -1.0f,
    0.0f,
    1.0f,
    -1.0f,
    0.0f,
    -1.0f,
    1.0f,
    0.0f,
    1.0f,
    1.0f,
    0.0f,

    // 纹理坐标,存储4个xy坐标
    // 坐标范围为[0,1],左下角为 0,0
    0.0f,
    1.0f,
    1.0f,
    1.0f,
    0.0f,
    0.0f,
    1.0f,
    0.0f
};

// 顶点着色器
static const QString s_vertShader = R"(
    attribute vec3 vertexIn;    // xyz顶点坐标
    attribute vec2 textureIn;   // xy纹理坐标
    varying vec2 textureOut;    // 传递给片段着色器的纹理坐标
    void main(void)
    {
        gl_Position = vec4(vertexIn, 1.0);  // 1.0表示vertexIn是一个顶点位置
        textureOut = textureIn; // 纹理坐标直接传递给片段着色器
    }
)";

// 片段着色器
static QString s_fragShader = R"(
    varying vec2 textureOut;        // 由顶点着色器传递过来的纹理坐标
    uniform sampler2D textureY;     // uniform 纹理单元,利用纹理单元可以使用多个纹理
    uniform sampler2D textureU;     // sampler2D是2D采样器
    uniform sampler2D textureV;     // 声明yuv三个纹理单元
    void main(void)
    {
        vec3 yuv;
        vec3 rgb;

        // SDL2 BT709_SHADER_CONSTANTS
        // https://github.com/spurious/SDL-mirror/blob/4ddd4c445aa059bb127e101b74a8c5b59257fbe2/src/render/opengl/SDL_shaders_gl.c#L102
        const vec3 Rcoeff = vec3(1.1644,  0.000,  1.7927);
        const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329);
        const vec3 Bcoeff = vec3(1.1644,  2.1124,  0.000);

        // 根据指定的纹理textureY和坐标textureOut来采样
        yuv.x = texture2D(textureY, textureOut).r;
        yuv.y = texture2D(textureU, textureOut).r - 0.5;
        yuv.z = texture2D(textureV, textureOut).r - 0.5;

        // 采样完转为rgb
        // 减少一些亮度
        yuv.x = yuv.x - 0.0625;
        rgb.r = dot(yuv, Rcoeff);
        rgb.g = dot(yuv, Gcoeff);
        rgb.b = dot(yuv, Bcoeff);
        // 输出颜色值
        gl_FragColor = vec4(rgb, 1.0);
    }
)";

QYUVOpenGLWidget::QYUVOpenGLWidget(QWidget *parent) : QOpenGLWidget(parent)
{
    /*
    QSurfaceFormat format = QSurfaceFormat::defaultFormat();
    format.setColorSpace(QSurfaceFormat::sRGBColorSpace);
    format.setProfile(QSurfaceFormat::CompatibilityProfile);
    format.setMajorVersion(3);
    format.setMinorVersion(2);
    QSurfaceFormat::setDefaultFormat(format);
    */
}

QYUVOpenGLWidget::~QYUVOpenGLWidget()
{
    makeCurrent();
    m_vbo.destroy();
    deInitTextures();
    doneCurrent();
}

QSize QYUVOpenGLWidget::minimumSizeHint() const
{
    return QSize(50, 50);
}

QSize QYUVOpenGLWidget::sizeHint() const
{
    return size();
}

void QYUVOpenGLWidget::setFrameSize(const QSize &frameSize)
{
    if (m_frameSize != frameSize) {
        m_frameSize = frameSize;
        m_needUpdate = true;
        // inittexture immediately
        repaint();
    }
}

const QSize &QYUVOpenGLWidget::frameSize()
{
    return m_frameSize;
}

void QYUVOpenGLWidget::updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV)
{
    if (m_textureInited) {
        updateTexture(m_texture[0], 0, dataY, linesizeY);
        updateTexture(m_texture[1], 1, dataU, linesizeU);
        updateTexture(m_texture[2], 2, dataV, linesizeV);
        update();
    }
}

void QYUVOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glDisable(GL_DEPTH_TEST);

    // 顶点缓冲对象初始化
    m_vbo.create();
    m_vbo.bind();
    m_vbo.allocate(coordinate, sizeof(coordinate));
    initShader();
    // 设置背景清理色为黑色
    glClearColor(0.0, 0.0, 0.0, 0.0);
    // 清理颜色背景
    glClear(GL_COLOR_BUFFER_BIT);
}

void QYUVOpenGLWidget::paintGL()
{
    m_shaderProgram.bind();

    if (m_needUpdate) {
        deInitTextures();
        initTextures();
        m_needUpdate = false;
    }

    if (m_textureInited) {
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, m_texture[0]);

        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, m_texture[1]);

        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, m_texture[2]);

        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    }

    m_shaderProgram.release();
}

void QYUVOpenGLWidget::resizeGL(int width, int height)
{
    glViewport(0, 0, width, height);
    repaint();
}

void QYUVOpenGLWidget::initShader()
{
    // opengles的float、int等要手动指定精度
    if (QCoreApplication::testAttribute(Qt::AA_UseOpenGLES)) {
        s_fragShader.prepend(R"(
                             precision mediump int;
                             precision mediump float;
                             )");
    }
    m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShader);
    m_shaderProgram.addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShader);
    m_shaderProgram.link();
    m_shaderProgram.bind();

    // 指定顶点坐标在vbo中的访问方式
    // 参数解释:顶点坐标在shader中的参数名称,顶点坐标为float,起始偏移为0,顶点坐标类型为vec3,步幅为3个float
    m_shaderProgram.setAttributeBuffer("vertexIn", GL_FLOAT, 0, 3, 3 * sizeof(float));
    // 启用顶点属性
    m_shaderProgram.enableAttributeArray("vertexIn");

    // 指定纹理坐标在vbo中的访问方式
    // 参数解释:纹理坐标在shader中的参数名称,纹理坐标为float,起始偏移为12个float(跳过前面存储的12个顶点坐标),纹理坐标类型为vec2,步幅为2个float
    m_shaderProgram.setAttributeBuffer("textureIn", GL_FLOAT, 12 * sizeof(float), 2, 2 * sizeof(float));
    m_shaderProgram.enableAttributeArray("textureIn");

    // 关联片段着色器中的纹理单元和opengl中的纹理单元(opengl一般提供16个纹理单元)
    m_shaderProgram.setUniformValue("textureY", 0);
    m_shaderProgram.setUniformValue("textureU", 1);
    m_shaderProgram.setUniformValue("textureV", 2);
}

void QYUVOpenGLWidget::initTextures()
{
    // 创建纹理
    glGenTextures(1, &m_texture[0]);
    glBindTexture(GL_TEXTURE_2D, m_texture[0]);
    // 设置纹理缩放时的策略
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 设置st方向上纹理超出坐标时的显示策略
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width(), m_frameSize.height(), 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    glGenTextures(1, &m_texture[1]);
    glBindTexture(GL_TEXTURE_2D, m_texture[1]);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    glGenTextures(1, &m_texture[2]);
    glBindTexture(GL_TEXTURE_2D, m_texture[2]);
    // 设置纹理缩放时的策略
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // 设置st方向上纹理超出坐标时的显示策略
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, m_frameSize.width() / 2, m_frameSize.height() / 2, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);

    m_textureInited = true;
}

void QYUVOpenGLWidget::deInitTextures()
{
    if (QOpenGLFunctions::isInitialized(QOpenGLFunctions::d_ptr)) {
        glDeleteTextures(3, m_texture);
    }

    memset(m_texture, 0, sizeof(m_texture));
    m_textureInited = false;
}

void QYUVOpenGLWidget::updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride)
{
    if (!pixels)
        return;

    QSize size = 0 == textureType ? m_frameSize : m_frameSize / 2;

    makeCurrent();
    glBindTexture(GL_TEXTURE_2D, texture);
    glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(stride));
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, size.width(), size.height(), GL_LUMINANCE, GL_UNSIGNED_BYTE, pixels);
    doneCurrent();
}


================================================
FILE: QtScrcpy/render/qyuvopenglwidget.h
================================================
#ifndef QYUVOPENGLWIDGET_H
#define QYUVOPENGLWIDGET_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>

class QYUVOpenGLWidget
    : public QOpenGLWidget
    , protected QOpenGLFunctions
{
    Q_OBJECT
public:
    explicit QYUVOpenGLWidget(QWidget *parent = nullptr);
    virtual ~QYUVOpenGLWidget() override;

    QSize minimumSizeHint() const override;
    QSize sizeHint() const override;

    void setFrameSize(const QSize &frameSize);
    const QSize &frameSize();
    void updateTextures(quint8 *dataY, quint8 *dataU, quint8 *dataV, quint32 linesizeY, quint32 linesizeU, quint32 linesizeV);

protected:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int width, int height) override;

private:
    void initShader();
    void initTextures();
    void deInitTextures();
    void updateTexture(GLuint texture, quint32 textureType, quint8 *pixels, quint32 stride);

private:
    // 视频帧尺寸
    QSize m_frameSize = { -1, -1 };
    bool m_needUpdate = false;
    bool m_textureInited = false;

    // 顶点缓冲对象(Vertex Buffer Objects, VBO):默认即为VertexBuffer(GL_ARRAY_BUFFER)类型
    QOpenGLBuffer m_vbo;

    // 着色器程序:编译链接着色器
    QOpenGLShaderProgram m_shaderProgram;

    // YUV纹理,用于生成纹理贴图
    GLuint m_texture[3] = { 0 };
};

#endif // QYUVOPENGLWIDGET_H


================================================
FILE: QtScrcpy/res/Info_Mac.plist.in
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>zh-Hans</string>
	<key>CFBundleExecutable</key>
	<string>QtScrcpy</string>
	<key>CFBundleGetInfoString</key>
	<string>Created by rankun</string>
	<key>CFBundleIconFile</key>
	<string>QtScrcpy</string>
	<key>CFBundleIdentifier</key>
	<string>rankun.QtScrcpy</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>QtScrcpy</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>${BUNDLE_VERSION}</string>
	<key>CFBundleSupportedPlatforms</key>
	<array>
		<string>MacOSX</string>
	</array>
	<key>CFBundleVersion</key>
	<string>${BUNDLE_VERSION}</string>
	<key>LSMinimumSystemVersion</key>
	<string>10.10</string>
	<key>NSAppleEventsUsageDescription</key>
	<string></string>
	<key>NSHumanReadableCopyright</key>
	<string>Copyright © 2018-2038 rankun. All rights reserved.</string>
	<key>NSMainStoryboardFile</key>
	<string>Main</string>
	<key>NSPrincipalClass</key>
	<string>NSApplication</string>
	<key>NSSupportsAutomaticGraphicsSwitching</key>
	<true/>
</dict>
</plist>


================================================
FILE: QtScrcpy/res/QtScrcpy.rc
================================================
#include "winres.h"

IDI_ICON1       ICON      "QtScrcpy.ico"
// GB2312编码的话,在中文系统上打包FileDescription可以显示中文
// 在github action(英文系统)打包后FileDescription是乱码,utf8编码也不行。。
VS_VERSION_INFO VERSIONINFO
 FILEVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH
 PRODUCTVERSION VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH
 FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
 FILEFLAGS 0x1L
#else
 FILEFLAGS 0x0L
#endif
 FILEOS 0x40004L
 FILETYPE 0x1L
 FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "080404b0"
        BEGIN
            VALUE "CompanyName", "RanKun"
            VALUE "FileDescription", "Android real-time display control software"
            VALUE "FileVersion", VERSION_RC_STR
            VALUE "LegalCopyright", "Copyright (C) RanKun 2018-2038. All rights reserved."
            VALUE "ProductName", "QtScrcpy"
            VALUE "ProductVersion", VERSION_RC_STR
        END
    END
    BLOCK "VarFileInfo"
    BEGIN
        VALUE "Translation", 0x804, 1200
    END
END


================================================
FILE: QtScrcpy/res/i18n/CMakeLists.txt
================================================
# 声明ts文件
set(QC_TS_FILES 
    ${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts 
    ${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts
    ${CMAKE_CURRENT_SOURCE_DIR}/ja_JP.ts
)
# 设置qm文件生成目录
set_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}")
# 引入LinguistTools
find_package(QT NAMES Qt6 Qt5 COMPONENTS LinguistTools REQUIRED)
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS LinguistTools REQUIRED)

# qt5_create_translation会依次执行 lupdate更新ts、lrelease更新qm
qt5_create_translation(QM_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${QC_TS_FILES})
# 自定义目标依赖QM_FILES,否则不会生成qm文件
add_custom_target(QC_QM_GENERATOR DEPENDS ${QM_FILES})

# qt5_create_translation的bug:cmake clean的时候会删除翻译好的ts文件,导致翻译丢失
# (qt官方说qt6没问题,只用qt6的可以考虑qt5_create_translation)
# 网上查到的CLEAN_NO_CUSTOM办法只能在makefile生成器下生效,解决不了问题
# https://cmake.org/cmake/help/latest/prop_dir/CLEAN_NO_CUSTOM.html
# set_directory_properties(PROPERTIES CLEAN_NO_CUSTOM true)
# 目前唯一的解决办法是每次clean后,都手动在git中恢复一下ts文件

#[[
总结:
cmake qt项目下,利用cmake脚本有三种方式处理翻译:
1. 完全使用qt自带的cmake LinguistTools脚本:qt5_create_translation&qt5_add_translation
这两个脚本都满足不了需求:
qt5_add_translation只能根据已有ts文件生成qm文件(lrelease),不能更新ts文件(lupdate)
qt5_create_translation在cmake clean的时候会删除翻译好的ts文件,导致翻译丢失

2. cmake add_custom_command + cmake LinguistTools脚本(其实qt5_create_translation内部使用的也是add_custom_command)
例如add_custom_command执行lupdate,配合qt5_add_translation更新qm,
参考:https://github.com/maratnek/QtFirstProgrammCMake/blob/2c93b59e2ba85ff6ee0e727487e14003381687d3/CMakeLists.txt

3. 完全使用cmake命令来执行lupdate和lrelease
例如add_custom_command/add_custom_target/execute_process都可以实现执行lupdate和lrelease命令

上面3个方案都有一个共同问题:就是翻译文件处理都是和编译绑定在一起的,每次编译都会检测执行,实际的翻译工作是所有
编程工作都完成以后,统一执行一次lupdate、翻译、lrelease就可以了,不应该和编译绑定在一起
所以写两个shell脚本lupdate.sh和lrelease.sh来处理比较合适,其实非常简单:
1. 更新ts:lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
2. 手动翻译ts
3. 发布:lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts

参考文档
1. qt知道qt5_create_translation的bug,但是不肯解决,只确定了qt6没问题 https://bugreports.qt.io/browse/QTBUG-96549
2. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-add-translation.html
3. https://doc.qt.io/qt-5/qtlinguist-cmake-qt5-create-translation.html
4. execute_process 参考:https://blog.csdn.net/u010255072/article/details/120326833
5. add_custom_target 参考:https://www.cnblogs.com/apocelipes/p/14355460.html


================================================
FILE: QtScrcpy/res/i18n/en_US.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
    <name>Dialog</name>
    <message>
        <source>show</source>
        <translation>show</translation>
    </message>
    <message>
        <source>quit</source>
        <translation>quit</translation>
    </message>
    <message>
        <source>original</source>
        <translation>original</translation>
    </message>
    <message>
        <source>no lock</source>
        <translation>no lock</translation>
    </message>
    <message>
        <source>Notice</source>
        <translation>Notice</translation>
    </message>
    <message>
        <source>Hidden here!</source>
        <translation>Hidden here!</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>select path</translation>
    </message>
    <message>
        <source>Clear History</source>
        <translation>Clear History</translation>
    </message>
</context>
<context>
    <name>QObject</name>
    <message>
        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
        <translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
    </message>
    <message>
        <source>QuickMirror</source>
        <translation>QuickMirror</translation>
    </message>
    <message>
        <source>If you need more professional batch control mirror software, you can try the following software:</source>
        <translation>If you need more professional batch control mirror software, you can try the following software:</translation>
    </message>
    <message>
        <source>If you need more professional game keymap mirror software, you can try the following software:</source>
        <translation>If you need more professional game keymap mirror software, you can try the following software:</translation>
    </message>
    <message>
        <source>QuickAssistant</source>
        <translation>QuickAssistant</translation>
    </message>
    <message>
        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
        <translation>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>
    </message>
</context>
<context>
    <name>ToolForm</name>
    <message>
        <source>Tool</source>
        <translation>Tool</translation>
    </message>
    <message>
        <source>full screen</source>
        <translation>full screen</translation>
    </message>
    <message>
        <source>expand notify</source>
        <translation>expand notify</translation>
    </message>
    <message>
        <source>touch switch</source>
        <translation>touch switch</translation>
    </message>
    <message>
        <source>close screen</source>
        <translation>close screen</translation>
    </message>
    <message>
        <source>power</source>
        <translation>power</translation>
    </message>
    <message>
        <source>volume up</source>
        <translation>volume up</translation>
    </message>
    <message>
        <source>volume down</source>
        <translation>volume down</translation>
    </message>
    <message>
        <source>app switch</source>
        <translation>app switch</translation>
    </message>
    <message>
        <source>menu</source>
        <translation>menu</translation>
    </message>
    <message>
        <source>home</source>
        <translation>home</translation>
    </message>
    <message>
        <source>return</source>
        <translation>return</translation>
    </message>
    <message>
        <source>screen shot</source>
        <translation>screen shot</translation>
    </message>
    <message>
        <source>open screen</source>
        <translation>open screen</translation>
    </message>
    <message>
        <source>group control</source>
        <translation>group control</translation>
    </message>
</context>
<context>
    <name>VideoForm</name>
    <message>
        <source>file does not exist</source>
        <translation>file does not exist</translation>
    </message>
</context>
<context>
    <name>Widget</name>
    <message>
        <source>Wireless</source>
        <translation>Wireless</translation>
    </message>
    <message>
        <source>wireless connect</source>
        <translation>wireless connect</translation>
    </message>
    <message>
        <source>wireless disconnect</source>
        <translation>wireless disconnect</translation>
    </message>
    <message>
        <source>Start Config</source>
        <translation>Start Config</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>select path</translation>
    </message>
    <message>
        <source>record format:</source>
        <translation>record format:</translation>
    </message>
    <message>
        <source>record screen</source>
        <translation>record screen</translation>
    </message>
    <message>
        <source>frameless</source>
        <translation>frameless</translation>
    </message>
    <message>
        <source>Use Simple Mode</source>
        <translatorcomment>Use Simple Mode</translatorcomment>
        <translation>Use Simple Mode</translation>
    </message>
    <message>
        <source>Simple Mode</source>
        <translatorcomment>Simple Mode</translatorcomment>
        <translation>Simple Mode</translation>
    </message>
    <message>
        <source>WIFI Connect</source>
        <translatorcomment>WIFI Connect</translatorcomment>
        <translation>WIFI Connect</translation>
    </message>
    <message>
        <source>USB Connect</source>
        <translatorcomment>USB Connect</translatorcomment>
        <translation>USB Connect</translation>
    </message>
    <message>
        <source>Double click to connect:</source>
        <translation>Double click to connect:</translation>
    </message>
    <message>
        <source>lock orientation:</source>
        <translation>lock orientation:</translation>
    </message>
    <message>
        <source>show fps</source>
        <translation>show fps</translation>
    </message>
    <message>
        <source>stay awake</source>
        <translation>stay awake</translation>
    </message>
    <message>
        <source>device name:</source>
        <translatorcomment>device name:</translatorcomment>
        <translation>device name:</translation>
    </message>
    <message>
        <source>update name</source>
        <translatorcomment>update name</translatorcomment>
        <translation>update name</translation>
    </message>
    <message>
        <source>stop all server</source>
        <translation>stop all server</translation>
    </message>
    <message>
        <source>adb command:</source>
        <translation>adb command:</translation>
    </message>
    <message>
        <source>terminate</source>
        <translation>terminate</translation>
    </message>
    <message>
        <source>execute</source>
        <translation>execute</translation>
    </message>
    <message>
        <source>clear</source>
        <translation>clear</translation>
    </message>
    <message>
        <source>reverse connection</source>
        <translation>reverse connection</translation>
    </message>
    <message>
        <source>background record</source>
        <translation>background record</translation>
    </message>
    <message>
        <source>screen-off</source>
        <translation>screen-off</translation>
    </message>
    <message>
        <source>apply</source>
        <translation>apply</translation>
    </message>
    <message>
        <source>max size:</source>
        <translation>max size:</translation>
    </message>
    <message>
        <source>always on top</source>
        <translation>always on top</translation>
    </message>
    <message>
        <source>refresh script</source>
        <translation>refresh script</translation>
    </message>
    <message>
        <source>get device IP</source>
        <translation>get device IP</translation>
    </message>
    <message>
        <source>USB line</source>
        <translation>USB line</translation>
    </message>
    <message>
        <source>stop server</source>
        <translation>stop server</translation>
    </message>
    <message>
        <source>start server</source>
        <translation>start server</translation>
    </message>
    <message>
        <source>device serial:</source>
        <translation>device serial:</translation>
    </message>
    <message>
        <source>bit rate:</source>
        <translation>bit rate:</translation>
    </message>
    <message>
        <source>start adbd</source>
        <translation>start adbd</translation>
    </message>
    <message>
        <source>refresh devices</source>
        <translation>refresh devices</translation>
    </message>
    <message>
        <source>install sndcpy</source>
        <translation>install sndcpy</translation>
    </message>
    <message>
        <source>start audio</source>
        <translation>start audio</translation>
    </message>
    <message>
        <source>stop audio</source>
        <translation>stop audio</translation>
    </message>
    <message>
        <source>auto update</source>
        <translation>auto update</translation>
    </message>
    <message>
        <source>show toolbar</source>
        <translation>show toolbar</translation>
    </message>
    <message>
        <source>record save path:</source>
        <translation>record save path:</translation>
    </message>
</context>
</TS>


================================================
FILE: QtScrcpy/res/i18n/ja_JP.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS []>
<TS version="2.1" language="ja_JP">
  <context>
    <name>Dialog</name>
    <message>
      <source>show</source>
      <translation>表示</translation>
    </message>
    <message>
      <source>quit</source>
      <translation>終了</translation>
    </message>
    <message>
      <source>original</source>
      <translation>オリジナル</translation>
    </message>
    <message>
      <source>no lock</source>
      <translation>ロックなし</translation>
    </message>
    <message>
      <source>Notice</source>
      <translation>お知らせ</translation>
    </message>
    <message>
      <source>Hidden here!</source>
      <translation>ここに隠れています!</translation>
    </message>
    <message>
      <source>select path</source>
      <translation>パスを選択</translation>
    </message>
    <message>
      <source>Clear History</source>
      <translation>履歴を消去</translation>
    </message>
  </context>
  <context>
    <name>QObject</name>
    <message>
      <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
      <translation>このソフトウェアはオープンソースで完全無料です。自己責任でご利用ください。以下のアドレスからダウンロードできます:</translation>
    </message>
    <message>
      <source>QuickMirror</source>
      <translation>クイックミラー</translation>
    </message>
    <message>
      <source>If you need more professional batch control mirror software, you can try the following software:</source>
      <translation>より高度なバッチ制御が可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>
    </message>
    <message>
      <source>If you need more professional game keymap mirror software, you can try the following software:</source>
      <translation>より高度なゲームキーマップが可能なミラーソフトウェアが必要な場合は、次のソフトウェアをお試しください:</translation>
    </message>
    <message>
      <source>QuickAssistant</source>
      <translation>クイックアシスタント</translation>
    </message>
    <message>
      <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
      <translation>Telegram で連絡ができます &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>
    </message>
  </context>
  <context>
    <name>ToolForm</name>
    <message>
      <source>Tool</source>
      <translation>ツール</translation>
    </message>
    <message>
      <source>full screen</source>
      <translation>フルスクリーン</translation>
    </message>
    <message>
      <source>expand notify</source>
      <translation>通知を展開</translation>
    </message>
    <message>
      <source>touch switch</source>
      <translation>タッチ切り替え</translation>
    </message>
    <message>
      <source>close screen</source>
      <translation>画面を閉じる</translation>
    </message>
    <message>
      <source>power</source>
      <translation>電源</translation>
    </message>
    <message>
      <source>volume up</source>
      <translation>音量を上げる</translation>
    </message>
    <message>
      <source>volume down</source>
      <translation>音量を下げる</translation>
    </message>
    <message>
      <source>app switch</source>
      <translation>アプリを切り替え</translation>
    </message>
    <message>
      <source>menu</source>
      <translation>メニュー</translation>
    </message>
    <message>
      <source>home</source>
      <translation>ホーム</translation>
    </message>
    <message>
      <source>return</source>
      <translation>戻る</translation>
    </message>
    <message>
      <source>screen shot</source>
      <translation>スクリーンショット</translation>
    </message>
    <message>
      <source>open screen</source>
      <translation>画面を開く</translation>
    </message>
    <message>
      <source>group control</source>
      <translation>グループコントロール</translation>
    </message>
  </context>
  <context>
    <name>VideoForm</name>
    <message>
      <source>file does not exist</source>
      <translation>ファイルが存在しません</translation>
    </message>
  </context>
  <context>
    <name>Widget</name>
    <message>
      <source>Wireless</source>
      <translation>ワイヤレス</translation>
    </message>
    <message>
      <source>wireless connect</source>
      <translation>ワイヤレスで接続</translation>
    </message>
    <message>
      <source>wireless disconnect</source>
      <translation>ワイヤレスを切断</translation>
    </message>
    <message>
      <source>Start Config</source>
      <translation>構成を開始</translation>
    </message>
    <message>
      <source>select path</source>
      <translation>パスを選択</translation>
    </message>
    <message>
      <source>record format:</source>
      <translation>録画の形式:</translation>
    </message>
    <message>
      <source>record screen</source>
      <translation>画面を録画</translation>
    </message>
    <message>
      <source>frameless</source>
      <translation>フレームレス</translation>
    </message>
    <message>
      <source>Use Simple Mode</source>
      <translatorcomment>シンプルモードを使用する</translatorcomment>
      <translation>シンプルモードを使用する</translation>
    </message>
    <message>
      <source>Simple Mode</source>
      <translatorcomment>シンプルモード</translatorcomment>
      <translation>シンプルモード</translation>
    </message>
    <message>
      <source>WIFI Connect</source>
      <translatorcomment>Wi-Fi 接続</translatorcomment>
      <translation>Wi-Fi 接続</translation>
    </message>
    <message>
      <source>USB Connect</source>
      <translatorcomment>USB 接続</translatorcomment>
      <translation>USB 接続</translation>
    </message>
    <message>
      <source>Double click to connect:</source>
      <translation>ダブルクリックで接続:</translation>
    </message>
    <message>
      <source>lock orientation:</source>
      <translation>画面方向をロック:</translation>
    </message>
    <message>
      <source>show fps</source>
      <translation>FPS を表示</translation>
    </message>
    <message>
      <source>stay awake</source>
      <translation>画面を常時点灯</translation>
    </message>
    <message>
      <source>device name:</source>
      <translatorcomment>デバイス名:</translatorcomment>
      <translation>デバイス名:</translation>
    </message>
    <message>
      <source>update name</source>
      <translatorcomment>更新名</translatorcomment>
      <translation>更新名</translation>
    </message>
    <message>
      <source>stop all server</source>
      <translation>すべてのサーバーを停止</translation>
    </message>
    <message>
      <source>adb command:</source>
      <translation>adb コマンド:</translation>
    </message>
    <message>
      <source>terminate</source>
      <translation>停止</translation>
    </message>
    <message>
      <source>execute</source>
      <translation>実行</translation>
    </message>
    <message>
      <source>clear</source>
      <translation>消去</translation>
    </message>
    <message>
      <source>reverse connection</source>
      <translation>リバース接続</translation>
    </message>
    <message>
      <source>background record</source>
      <translation>バックグラウンド録画</translation>
    </message>
    <message>
      <source>screen-off</source>
      <translation>画面を OFF</translation>
    </message>
    <message>
      <source>apply</source>
      <translation>適用</translation>
    </message>
    <message>
      <source>max size:</source>
      <translation>最大サイズ:</translation>
    </message>
    <message>
      <source>always on top</source>
      <translation>常に手前に表示</translation>
    </message>
    <message>
      <source>refresh script</source>
      <translation>スクリプトを更新</translation>
    </message>
    <message>
      <source>get device IP</source>
      <translation>デバイス IP を取得</translation>
    </message>
    <message>
      <source>USB line</source>
      <translation>USB ライン</translation>
    </message>
    <message>
      <source>stop server</source>
      <translation>サーバーを停止</translation>
    </message>
    <message>
      <source>start server</source>
      <translation>サーバーを開始</translation>
    </message>
    <message>
      <source>device serial:</source>
      <translation>デバイスシリアル:</translation>
    </message>
    <message>
      <source>bit rate:</source>
      <translation>ビットレート:</translation>
    </message>
    <message>
      <source>start adbd</source>
      <translation>adbd を開始</translation>
    </message>
    <message>
      <source>refresh devices</source>
      <translation>デバイスを更新</translation>
    </message>
    <message>
      <source>install sndcpy</source>
      <translation>Sndcpy をインストール</translation>
    </message>
    <message>
      <source>start audio</source>
      <translation>オーディオを開始</translation>
    </message>
    <message>
      <source>stop audio</source>
      <translation>オーディオを停止</translation>
    </message>
    <message>
      <source>auto update</source>
      <translation>自動更新</translation>
    </message>
    <message>
      <source>show toolbar</source>
      <translation>ツールバーを表示</translation>
    </message>
    <message>
      <source>record save path:</source>
      <translation>録画の保存先:</translation>
    </message>
  </context>
</TS>

================================================
FILE: QtScrcpy/res/i18n/ko_KR.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ko">
<context>
    <name>Dialog</name>
    <message>
        <source>show</source>
        <translation>표시</translation>
    </message>
    <message>
        <source>quit</source>
        <translation>종료</translation>
    </message>
    <message>
        <source>original</source>
        <translation>원본</translation>
    </message>
    <message>
        <source>no lock</source>
        <translation>잠금 없음</translation>
    </message>
    <message>
        <source>Notice</source>
        <translation>알림</translation>
    </message>
    <message>
        <source>Hidden here!</source>
        <translation>여기에 숨겨져 있어요!</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>경로 선택</translation>
    </message>
    <message>
        <source>Clear History</source>
        <translation>기록 지우기</translation>
    </message>
</context>
<context>
    <name>QObject</name>
    <message>
        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
        <translation>이 소프트웨어는 완전히 오픈 소스이며 무료입니다. 본인의 위험을 감수하고 사용하세요. 다음 주소로 다운로드할 수 있습니다:</translation>
    </message>
    <message>
        <source>QuickMirror</source>
        <translation>빠른미러</translation>
    </message>
    <message>
        <source>If you need more professional batch control mirror software, you can try the following software:</source>
        <translation>더 전문적인 일괄 제어 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 볼 수 있습니다:</translation>
    </message>
    <message>
        <source>If you need more professional game keymap mirror software, you can try the following software:</source>
        <translation>더 전문적인 게임 키맵 미러 소프트웨어가 필요하다면 다음 소프트웨어를 사용해 보세요:</translation>
    </message>
    <message>
        <source>QuickAssistant</source>
        <translation>빠른 도우미</translation>
    </message>
    <message>
        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
        <translation>텔레그램으로 연락주세요 &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</translation>
    </message>
</context>
<context>
    <name>ToolForm</name>
    <message>
        <source>Tool</source>
        <translation>도구</translation>
    </message>
    <message>
        <source>full screen</source>
        <translation>전체 화면</translation>
    </message>
    <message>
        <source>expand notify</source>
        <translation>확장 알림</translation>
    </message>
    <message>
        <source>touch switch</source>
        <translation>터치 스위치</translation>
    </message>
    <message>
        <source>close screen</source>
        <translation>화면 닫기</translation>
    </message>
    <message>
        <source>power</source>
        <translation>전ㅇ</translation>
    </message>
    <message>
        <source>volume up</source>
        <translation>볼륨 높이기</translation>
    </message>
    <message>
        <source>volume down</source>
        <translation>볼륨 낮추기</translation>
    </message>
    <message>
        <source>app switch</source>
        <translation>앱 스위치</translation>
    </message>
    <message>
        <source>menu</source>
        <translation>메뉴</translation>
    </message>
    <message>
        <source>home</source>
        <translation>home</translation>
    </message>
    <message>
        <source>return</source>
        <translation>돌ㅇ가기</translation>
    </message>
    <message>
        <source>screen shot</source>
        <translation>스크린샷</translation>
    </message>
    <message>
        <source>open screen</source>
        <translation>화면 열기</translation>
    </message>
    <message>
        <source>group control</source>
        <translation>그룹 제어</translation>
    </message>
</context>
<context>
    <name>VideoForm</name>
    <message>
        <source>file does not exist</source>
        <translation>파일이 존재하지 않습니다</translation>
    </message>
</context>
<context>
    <name>Widget</name>
    <message>
        <source>Wireless</source>
        <translation>무선</translation>
    </message>
    <message>
        <source>wireless connect</source>
        <translation>무선 연결</translation>
    </message>
    <message>
        <source>wireless disconnect</source>
        <translation>무선 연결 끊기</translation>
    </message>
    <message>
        <source>Start Config</source>
        <translation>시작 구성</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>경로 선택</translation>
    </message>
    <message>
        <source>record format:</source>
        <translation>기록 형식:</translation>
    </message>
    <message>
        <source>record screen</source>
        <translation>화면 녹화</translation>
    </message>
    <message>
        <source>frameless</source>
        <translation>프레임 없음</translation>
    </message>
    <message>
        <source>Use Simple Mode</source>
        <translatorcomment>Use Simple Mode</translatorcomment>
        <translation>간단한 모드 사용</translation>
    </message>
    <message>
        <source>Simple Mode</source>
        <translatorcomment>Simple Mode</translatorcomment>
        <translation>간단한 모드</translation>
    </message>
    <message>
        <source>WIFI Connect</source>
        <translatorcomment>WIFI Connect</translatorcomment>
        <translation>WIFI 연결</translation>
    </message>
    <message>
        <source>USB Connect</source>
        <translatorcomment>USB Connect</translatorcomment>
        <translation>USB 연결</translation>
    </message>
    <message>
        <source>Double click to connect:</source>
        <translation>더블 클릭하여 연결:</translation>
    </message>
    <message>
        <source>lock orientation:</source>
        <translation>잠금 방향:</translation>
    </message>
    <message>
        <source>show fps</source>
        <translation>fps 표시</translation>
    </message>
    <message>
        <source>stay awake</source>
        <translation>깨어 있기</translation>
    </message>
    <message>
        <source>device name:</source>
        <translatorcomment>device name:</translatorcomment>
        <translation>장치 이름:</translation>
    </message>
    <message>
        <source>update name</source>
        <translatorcomment>update name</translatorcomment>
        <translation>업데이트 이름</translation>
    </message>
    <message>
        <source>stop all server</source>
        <translation>모든 서버 중지</translation>
    </message>
    <message>
        <source>adb command:</source>
        <translation>adb 명령:</translation>
    </message>
    <message>
        <source>terminate</source>
        <translation>끝내기</translation>
    </message>
    <message>
        <source>execute</source>
        <translation>실행</translation>
    </message>
    <message>
        <source>clear</source>
        <translation>지우기</translation>
    </message>
    <message>
        <source>reverse connection</source>
        <translation>역연결</translation>
    </message>
    <message>
        <source>background record</source>
        <translation>배경 기록</translation>
    </message>
    <message>
        <source>screen-off</source>
        <translation>화면 끄기</translation>
    </message>
    <message>
        <source>apply</source>
        <translation>적용</translation>
    </message>
    <message>
        <source>max size:</source>
        <translation>최대 크기:</translation>
    </message>
    <message>
        <source>always on top</source>
        <translation>항상 위에</translation>
    </message>
    <message>
        <source>refresh script</source>
        <translation>스크립트 새로 고침</translation>
    </message>
    <message>
        <source>get device IP</source>
        <translation>장치 IP 가져오기</translation>
    </message>
    <message>
        <source>USB line</source>
        <translation>USB 선</translation>
    </message>
    <message>
        <source>stop server</source>
        <translation>서버 중지</translation>
    </message>
    <message>
        <source>start server</source>
        <translation>서버 시작</translation>
    </message>
    <message>
        <source>device serial:</source>
        <translation>장치 직렬:</translation>
    </message>
    <message>
        <source>bit rate:</source>
        <translation>비트 전송률:</translation>
    </message>
    <message>
        <source>start adbd</source>
        <translation>adbd 시작</translation>
    </message>
    <message>
        <source>refresh devices</source>
        <translation>장치 새로 고침</translation>
    </message>
    <message>
        <source>install sndcpy</source>
        <translation>sndcpy 설치</translation>
    </message>
    <message>
        <source>start audio</source>
        <translation>오디오 시작</translation>
    </message>
    <message>
        <source>stop audio</source>
        <translation>오디오 중지</translation>
    </message>
    <message>
        <source>auto update</source>
        <translation>자동 업데이트</translation>
    </message>
    <message>
        <source>show toolbar</source>
        <translation>도구 모음 표시</translation>
    </message>
    <message>
        <source>record save path:</source>
        <translation>기록 저장 경로:</translation>
    </message>
</context>
</TS>


================================================
FILE: QtScrcpy/res/i18n/zh_CN.ts
================================================
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
    <name>Dialog</name>
    <message>
        <source>show</source>
        <translation>显示</translation>
    </message>
    <message>
        <source>quit</source>
        <translation>退出</translation>
    </message>
    <message>
        <source>original</source>
        <translation>原始</translation>
    </message>
    <message>
        <source>no lock</source>
        <translation>不锁定</translation>
    </message>
    <message>
        <source>Notice</source>
        <translation>提示</translation>
    </message>
    <message>
        <source>Hidden here!</source>
        <translation>安卓录屏程序隐藏在这!</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>选择路径</translation>
    </message>
    <message>
        <source>Clear History</source>
        <translation>清理历史</translation>
    </message>
</context>
<context>
    <name>QObject</name>
    <message>
        <source>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</source>
        <translation>本软件完全开源免费,作者不对使用该软件产生的一切后果负责。你可以在以下地址下载:</translation>
    </message>
    <message>
        <source>QuickMirror</source>
        <translation>极限投屏</translation>
    </message>
    <message>
        <source>If you need more professional batch control mirror software, you can try the following software:</source>
        <translation>如果你需要更专业的批量控制投屏软件,你可以尝试下面软件:</translation>
    </message>
    <message>
        <source>If you need more professional game keymap mirror software, you can try the following software:</source>
        <translation>如果你需要更专业的游戏映射投屏软件,你可以尝试下面软件:</translation>
    </message>
    <message>
        <source>QuickAssistant</source>
        <translation>极限手游助手</translation>
    </message>
    <message>
        <source>You can contact me with telegram &lt;https://t.me/+Ylf_5V_rDCMyODQ1&gt;</source>
        <translation>你可以通过QQ群联系我 &lt;901736468&gt;</translation>
    </message>
</context>
<context>
    <name>ToolForm</name>
    <message>
        <source>Tool</source>
        <translation>工具</translation>
    </message>
    <message>
        <source>full screen</source>
        <translation>全屏</translation>
    </message>
    <message>
        <source>expand notify</source>
        <translation>下拉通知</translation>
    </message>
    <message>
        <source>touch switch</source>
        <translation>触摸显示开关</translation>
    </message>
    <message>
        <source>close screen</source>
        <translation>关闭屏幕</translation>
    </message>
    <message>
        <source>power</source>
        <translation>电源</translation>
    </message>
    <message>
        <source>volume up</source>
        <translation>音量加</translation>
    </message>
    <message>
        <source>volume down</source>
        <translation>音量减</translation>
    </message>
    <message>
        <source>app switch</source>
        <translation>切换应用</translation>
    </message>
    <message>
        <source>menu</source>
        <translation>菜单</translation>
    </message>
    <message>
        <source>home</source>
        <translation>主界面</translation>
    </message>
    <message>
        <source>return</source>
        <translation>返回</translation>
    </message>
    <message>
        <source>screen shot</source>
        <translation>截图</translation>
    </message>
    <message>
        <source>open screen</source>
        <translation>打开屏幕</translation>
    </message>
    <message>
        <source>group control</source>
        <translation>群控</translation>
    </message>
</context>
<context>
    <name>VideoForm</name>
    <message>
        <source>file does not exist</source>
        <translation>文件不存在</translation>
    </message>
</context>
<context>
    <name>Widget</name>
    <message>
        <source>Wireless</source>
        <translation>无线</translation>
    </message>
    <message>
        <source>wireless connect</source>
        <translation>无线连接</translation>
    </message>
    <message>
        <source>wireless disconnect</source>
        <translation>无线断开</translation>
    </message>
    <message>
        <source>Start Config</source>
        <translation>启动配置</translation>
    </message>
    <message>
        <source>select path</source>
        <translation>选择路径</translation>
    </message>
    <message>
        <source>record format:</source>
        <translation>录制格式:</translation>
    </message>
    <message>
        <source>record screen</source>
        <translation>录制屏幕</translation>
    </message>
    <message>
        <source>frameless</source>
        <translation>无边框</translation>
    </message>
    <message>
        <source>Use Simple Mode</source>
        <translatorcomment>启用精简模式</translatorcomment>
        <translation>启用精简模式</translation>
    </message>
    <message>
        <source>Simple Mode</source>
        <translatorcomment>精简模式</translatorcomment>
        <translation>精简模式</translation>
    </message>
    <message>
        <source>WIFI Connect</source>
        <translatorcomment>一键WIFI连接</translatorcomment>
        <translation>一键WIFI连接</translation>
    </message>
    <message>
        <source>USB Connect</source>
        <translatorcomment>一键USB连接</translatorcomment>
        <translation>一键USB连接</translation>
    </message>
    <message>
        <source>Double click to connect:</source>
        <translation>双击连接:</translation>
    </message>
    <message>
        <source>lock orientation:</source>
        <translation>锁定方向:</translation>
    </message>
    <message>
        <source>show fps</source>
        <translation>显示fps</translation>
    </message>
    <message>
        <source>stay awake</source>
        <translation>保持唤醒</translation>
    </message>
    <message>
        <source>device name:</source>
        <translatorcomment>设备名称:</translatorcomment>
        <translation>设备名称:</translation>
    </message>
    <message>
        <source>update name</source>
        <translatorcomment>更新设置名称</translatorcomment>
        <translation>更新设置名称</translation>
    </message>
    <message>
        <source>stop all server</source>
        <translation>停止所有服务</translation>
    </message>
    <message>
        <source>adb command:</source>
        <translation>adb命令:</translation>
    </message>
    <message>
        <source>terminate</source>
        <translation>终止</translation>
    </message>
    <message>
        <source>execute</source>
        <translation>执行</translation>
    </message>
    <message>
        <source>clear</source>
        <translation>清理</translation>
    </message>
    <message>
        <source>reverse connection</source>
        <translation>反向连接</translation>
    </message>
    <message>
        <source>background record</source>
        <translation>后台录制</translation>
    </message>
    <message>
        <source>screen-off</source>
        <translation>自动息屏</translation>
    </message>
    <message>
        <source>apply</source>
        <translation>应用脚本</translation>
    </message>
    <message>
        <source>max size:</source>
        <translation>最大尺寸:</translation>
    </message>
    <message>
        <source>always on top</source>
        <translation>窗口置顶</translation>
    </message>
    <message>
        <source>refresh script</source>
        <translation>刷新脚本</translation>
    </message>
    <message>
        <source>get device IP</source>
        <translation>获取设备IP</translation>
    </message>
    <message>
        <source>USB line</source>
        <translation>USB线</translation>
    </message>
    <message>
        <source>stop server</source>
        <translation>停止服务</translation>
    </message>
    <message>
        <source>start server</source>
        <translation>启动服务</translation>
    </message>
    <message>
        <source>device serial:</source>
        <translation>设备序列号:</translation>
    </message>
    <message>
        <source>bit rate:</source>
        <translation>比特率:</translation>
    </message>
    <message>
        <source>start adbd</source>
        <translation>启动adbd</translation>
    </message>
    <message>
        <source>refresh devices</source>
        <translation>刷新设备列表</translation>
    </message>
    <message>
        <source>install sndcpy</source>
        <translation>安装sndcpy</translation>
    </message>
    <message>
        <source>start audio</source>
        <translation>开始音频</translation>
    </message>
    <message>
        <source>stop audio</source>
        <translation>停止音频</translation>
    </message>
    <message>
        <source>auto update</source>
        <translation>自动刷新</translation>
    </message>
    <message>
        <source>show toolbar</source>
        <translation>显示工具栏</translation>
    </message>
    <message>
        <source>record save path:</source>
        <translation>录像保存路径</translation>
    </message>
</context>
</TS>


================================================
FILE: QtScrcpy/res/qss/psblack.css
================================================
QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}

QWidget[form="true"],QLabel[frameShape="1"]{
border:1px solid #242424;
border-radius:0px;
}

QWidget[form="bottom"]{
background:#484848;
}

QWidget[form="bottom"] .QFrame{
border:1px solid #DCDCDC;
}

QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{
border-radius:0px;
color:#DCDCDC;
background:none;
border-style:none;
}

QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{
border-style:none;
border-radius:0px;
padding:5px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{
border-style:solid;
border-width:0px 0px 2px 0px;
padding:4px 4px 2px 4px;
border-color:#00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QWidget[nav="left"] QAbstractButton{
border-radius:0px;
color:#DCDCDC;
background:none;
border-style:none;
}

QWidget[nav="left"] QAbstractButton:hover{
color:#FFFFFF;
background-color:#00BB9E;
}

QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{
color:#DCDCDC;
border-style:solid;
border-width:0px 0px 0px 2px;
padding:4px 4px 4px 2px;
border-color:#00BB9E;
background-color:#444444;
}

QWidget[video="true"] QLabel{
color:#DCDCDC;
border:1px solid #242424;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QWidget[video="true"] QLabel:focus{
border:1px solid #00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
border:1px solid #242424;
border-radius:3px;
padding:2px;
background:none;
selection-background-color:#264F78;
selection-color:#DCDCDC;
}

QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{
border:1px solid #242424;
}

QLineEdit[echoMode="2"]{
lineedit-password-character:9679;
}

.QFrame{
border:1px solid #242424;
border-radius:3px;
}

.QGroupBox{
border:1px solid #242424;
border-radius:5px;
margin-top:3ex;
}

.QGroupBox::title{
subcontrol-origin:margin;
position:relative;
left:10px;
}

.QPushButton,.QToolButton{
border-style:none;
border:1px solid #242424;
color:#DCDCDC;
padding:5px;
min-height:15px;
border-radius:5px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

.QPushButton:hover,.QToolButton:hover{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

.QPushButton:pressed,.QToolButton:pressed{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

.QToolButton::menu-indicator{
image:None;
}

QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{
border-radius:3px;
color:#DCDCDC;
padding:3px;
margin:0px;
background:none;
border-style:none;
}

QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(51,127,209,230);
}

QPushButton#btnMenu_Close:hover{
color:#FFFFFF;
margin:1px 1px 2px 1px;
background-color:rgba(238,0,0,128);
}

QRadioButton::indicator{
width:15px;
height:15px;
}

QRadioButton::indicator::unchecked{
image:url(:/qss/psblack/radiobutton_unchecked.png);
}

QRadioButton::indicator::unchecked:disabled{
image:url(:/qss/psblack/radiobutton_unchecked_disable.png);
}

QRadioButton::indicator::checked{
image:url(:/qss/psblack/radiobutton_checked.png);
}

QRadioButton::indicator::checked:disabled{
image:url(:/qss/psblack/radiobutton_checked_disable.png);
}

QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{
padding:0px -3px 0px 3px;
}

QCheckBox::indicator,QGroupBox::indicator,QTreeWidget::indicator,QListWidget::indicator{
width:13px;
height:13px;
}

QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeWidget::indicator:unchecked,QListWidget::indicator:unchecked{
image:url(:/qss/psblack/checkbox_unchecked.png);
}

QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeWidget::indicator:unchecked:disabled,QListWidget::indicator:disabled{
image:url(:/qss/psblack/checkbox_unchecked_disable.png);
}

QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeWidget::indicator:checked,QListWidget::indicator:checked{
image:url(:/qss/psblack/checkbox_checked.png);
}

QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeWidget::indicator:checked:disabled,QListWidget::indicator:checked:disabled{
image:url(:/qss/psblack/checkbox_checked_disable.png);
}

QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeWidget::indicator:indeterminate,QListWidget::indicator:indeterminate{
image:url(:/qss/psblack/checkbox_parcial.png);
}

QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeWidget::indicator:indeterminate:disabled,QListWidget::indicator:indeterminate:disabled{
image:url(:/qss/psblack/checkbox_parcial_disable.png);
}

QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{
image:url(:/qss/psblack/add_top.png);
width:10px;
height:10px;
padding:2px 5px 0px 0px;
}

QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{
image:url(:/qss/psblack/add_bottom.png);
width:10px;
height:10px;
padding:0px 5px 2px 0px;
}

QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{
top:-2px;
}
  
QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{
bottom:-2px;
}

QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{
image:url(:/qss/psblack/add_bottom.png);
width:10px;
height:10px;
right:2px;
}

QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{
subcontrol-origin:padding;
subcontrol-position:top right;
width:15px;
border-left-width:0px;
border-left-style:solid;
border-top-right-radius:3px;
border-bottom-right-radius:3px;
border-left-color:#242424;
}

QComboBox::drop-down:on{
top:1px;
}

QMenuBar::item{
color:#DCDCDC;
background-color:#484848;
margin:0px;
padding:3px 10px;
}

QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{
color:#DCDCDC;
background-color:#484848;
border:1px solid #242424;
margin:0px;
}

QMenu::item{
padding:3px 20px;
}

QMenu::indicator{
width:13px;
height:13px;
}

QMenu::item:selected,QMenuBar::item:selected{
color:#DCDCDC;
border:0px solid #242424;
background:#646464;
}

QMenu::separator{
height:1px;
background:#242424;
}

QProgressBar{
min-height:10px;
background:#484848;
border-radius:5px;
text-align:center;
border:1px solid #484848;
}

QProgressBar:chunk{
border-radius:5px;
background-color:#242424;
}

QSlider::groove:horizontal{
background:#484848;
height:8px;
border-radius:4px;
}

QSlider::add-page:horizontal{
background:#484848;
height:8px;
border-radius:4px;
}

QSlider::sub-page:horizontal{
background:#242424;
height:8px;
border-radius:4px;
}

QSlider::handle:horizontal{
width:13px;
margin-top:-3px;
margin-bottom:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);
}

QSlider::groove:vertical{
width:8px;
border-radius:4px;
background:#484848;
}

QSlider::add-page:vertical{
width:8px;
border-radius:4px;
background:#484848;
}

QSlider::sub-page:vertical{
width:8px;
border-radius:4px;
background:#242424;
}

QSlider::handle:vertical{
height:14px;
margin-left:-3px;
margin-right:-3px;
border-radius:6px;
background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424);
}

QScrollBar:horizontal{
background:#484848;
padding:0px;
border-radius:6px;
max-height:12px;
}

QScrollBar::handle:horizontal{
background:#525252;
min-width:50px;
border-radius:6px;
}

QScrollBar::handle:horizontal:hover{
background:#242424;
}

QScrollBar::handle:horizontal:pressed{
background:#242424;
}

QScrollBar::add-page:horizontal{
background:none;
}

QScrollBar::sub-page:horizontal{
background:none;
}

QScrollBar::add-line:horizontal{
background:none;
}

QScrollBar::sub-line:horizontal{
background:none;
}

QScrollBar:vertical{
background:#484848;
padding:0px;
border-radius:6px;
max-width:12px;
}

QScrollBar::handle:vertical{
background:#525252;
min-height:50px;
border-radius:6px;
}

QScrollBar::handle:vertical:hover{
background:#242424;
}

QScrollBar::handle:vertical:pressed{
background:#242424;
}

QScrollBar::add-page:vertical{
background:none;
}

QScrollBar::sub-page:vertical{
background:none;
}

QScrollBar::add-line:vertical{
background:none;
}

QScrollBar::sub-line:vertical{
background:none;
}

QScrollArea{
border:0px;
}

QTreeView,QListView,QTableView,QTabWidget::pane{
border:1px solid #242424;
selection-background-color:#646464;
selection-color:#DCDCDC;
alternate-background-color:#525252;
gridline-color:#242424;
}

QTreeView::branch:closed:has-children{
margin:4px;
border-image:url(:/qss/psblack/branch_open.png);
}

QTreeView::branch:open:has-children{
margin:4px;
border-image:url(:/qss/psblack/branch_close.png);
}

QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{
background:#444444;
}

QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QTableView::item:hover,QListView::item:hover,QTreeView::item:hover{
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QTableView::item,QListView::item,QTreeView::item{
padding:1px;
margin:0px;
}

QHeaderView::section,QTableCornerButton:section{
padding:3px;
margin:0px;
color:#DCDCDC;
border:1px solid #242424;
border-left-width:0px;
border-right-width:1px;
border-top-width:0px;
border-bottom-width:1px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QTabBar::tab{
border:1px solid #242424;
color:#DCDCDC;
margin:0px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QTabBar::tab:selected,QTabBar::tab:hover{
border-style:solid;
border-color:#00BB9E;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QTabBar::tab:top,QTabBar::tab:bottom{
padding:3px 8px 3px 8px;
}

QTabBar::tab:left,QTabBar::tab:right{
padding:8px 3px 8px 3px;
}

QTabBar::tab:top:selected,QTabBar::tab:top:hover{
border-width:2px 0px 0px 0px;
}

QTabBar::tab:right:selected,QTabBar::tab:right:hover{
border-width:0px 0px 0px 2px;
}

QTabBar::tab:bottom:selected,QTabBar::tab:bottom:hover{
border-width:0px 0px 2px 0px;
}

QTabBar::tab:left:selected,QTabBar::tab:left:hover{
border-width:0px 2px 0px 0px;
}

QTabBar::tab:first:top:selected,QTabBar::tab:first:top:hover,QTabBar::tab:first:bottom:selected,QTabBar::tab:first:bottom:hover{
border-left-width:1px;
border-left-color:#242424;
}

QTabBar::tab:first:left:selected,QTabBar::tab:first:left:hover,QTabBar::tab:first:right:selected,QTabBar::tab:first:right:hover{
border-top-width:1px;
border-top-color:#242424;
}

QTabBar::tab:last:top:selected,QTabBar::tab:last:top:hover,QTabBar::tab:last:bottom:selected,QTabBar::tab:last:bottom:hover{
border-right-width:1px;
border-right-color:#242424;
}

QTabBar::tab:last:left:selected,QTabBar::tab:last:left:hover,QTabBar::tab:last:right:selected,QTabBar::tab:last:right:hover{
border-bottom-width:1px;
border-bottom-color:#242424;
}

QStatusBar::item{
border:0px solid #484848;
border-radius:3px;
}

QToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{
padding:3px;
border-radius:5px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QToolTip{
border:0px solid #DCDCDC;
padding:1px;
color:#DCDCDC;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QToolBox::tab:selected{
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252);
}

QPrintPreviewDialog QToolButton{
border:0px solid #DCDCDC;
border-radius:0px;
margin:0px;
padding:3px;
background:none;
}

QColorDialog QPushButton,QFileDialog QPushButton{
min-width:80px;
}

QToolButton#qt_calendar_prevmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/psblack/calendar_prevmonth.png);
}

QToolButton#qt_calendar_nextmonth{
icon-size:0px;
min-width:20px;
image:url(:/qss/psblack/calendar_nextmonth.png);
}

QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{
border:0px solid #DCDCDC;
border-radius:3px;
margin:3px 3px 3px 3px;
padding:3px;
background:none;
}

QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{
border:1px solid #242424;
}

QCalendarWidget QSpinBox#qt_calendar_yearedit{
margin:2px;
}

QCalendarWidget QToolButton::menu-indicator{
image:None;
}

QCalendarWidget QTableView{
border-width:0px;
}

QCalendarWidget QWidget#qt_calendar_navigationbar{
border:1px solid #242424;
border-width:1px 1px 0px 1px;
background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838);
}

QComboBox QAbstractItemView::item{
min-height:20px;
min-width:10px;
}

QTableView[model="true"]::item{
padding:0px;
margin:0px;
}

QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{
border-width:0px;
border-radius:0px;
}

QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{
border-width:0px;
border-radius:0px;
}

QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{
background:#444444;
}

*:disabled{
background:#444444;
border-color:#484848;
}

QMessageBox {
background-color:#444444;
color:#DCDCDC;
}

/*TextColor:#DCDCDC*/
/*PanelColor:#444444*/
/*BorderColor:#242424*/
/*NormalColorStart:#484848*/
/*NormalColorEnd:#383838*/
/*DarkColorStart:#646464*/
/*DarkColorEnd:#525252*/
/*HighColor:#00BB9E*/


================================================
FILE: QtScrcpy/res/res.qrc
================================================
<RCC>
    <qresource prefix="/">
        <file>font/fontawesome-webfont.ttf</file>
        <file>image/videoform/phone-h.png</file>
        <file>image/videoform/phone-v.png</file>
        <file>qss/psblack.css</file>
        <file>qss/psblack/add_bottom.png</file>
        <file>qss/psblack/add_left.png</file>
        <file>qss/psblack/add_right.png</file>
        <file>qss/psblack/add_top.png</file>
        <file>qss/psblack/branch_close.png</file>
        <file>qss/psblack/branch_open.png</file>
        <file>qss/psblack/calendar_nextmonth.png</file>
        <file>qss/psblack/calendar_prevmonth.png</file>
        <file>qss/psblack/checkbox_checked.png</file>
        <file>qss/psblack/checkbox_checked_disable.png</file>
        <file>qss/psblack/checkbox_parcial.png</file>
        <file>qss/psblack/checkbox_parcial_disable.png</file>
        <file>qss/psblack/checkbox_unchecked.png</file>
        <file>qss/psblack/checkbox_unchecked_disable.png</file>
        <file>qss/psblack/radiobutton_checked.png</file>
        <file>qss/psblack/radiobutton_checked_disable.png</file>
        <file>qss/psblack/radiobutton_unchecked.png</file>
        <file>qss/psblack/radiobutton_unchecked_disable.png</file>
        <file>i18n/en_US.qm</file>
        <file>i18n/zh_CN.qm</file>
        <file>i18n/ja_JP.qm</file>
        <file>image/tray/logo.png</file>
    </qresource>
</RCC>


================================================
FILE: QtScrcpy/sndcpy/sndcpy.bat
================================================
@echo off

echo Begin Runing...
set SNDCPY_PORT=28200
set SNDCPY_APK=sndcpy.apk
set ADB=adb.exe

if not "%1"=="" (
    set serial=-s %1
)
if not "%2"=="" (
    set SNDCPY_PORT=%2
)

echo Waiting for device %1...
%ADB% %serial% wait-for-device || goto :error
echo Find device %1

for /f "delims=" %%i in ('%ADB% %serial% shell pm path com.rom1v.sndcpy') do set sndcpy_installed=%%i
if "%sndcpy_installed%"=="" (
    echo Install %SNDCPY_APK%... 
    %ADB% %serial% uninstall com.rom1v.sndcpy || echo uninstall failed
    %ADB% %serial% install -t -r -g %SNDCPY_APK% || goto :error
    echo Install %SNDCPY_APK% success
)

echo Request PROJECT_MEDIA permission...
%ADB% %serial% shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow

echo Forward port %SNDCPY_PORT%...
%ADB% %serial% forward tcp:%SNDCPY_PORT% localabstract:sndcpy || goto :error

echo Start %SNDCPY_APK%...
%ADB% %serial% shell am start com.rom1v.sndcpy/.MainActivity || goto :error

:check_start
echo Waiting %SNDCPY_APK% start...
::timeout /T 1 /NOBREAK > nul
%ADB% %serial% shell sleep 0.1
for /f "delims=" %%i in ("%ADB% shell 'ps | grep com.rom1v.sndcpy'") do set sndcpy_started=%%i
if "%sndcpy_started%"=="" (
    goto :check_start
)
echo %SNDCPY_APK% started...

echo Ready playing...
::vlc.exe -Idummy --demux rawaud --network-caching=0 --play-and-exit tcp://localhost:%SNDCPY_PORT%
::ffplay.exe -nodisp -autoexit -probesize 32 -sync ext -f s16le -ar 48k -ac 2 tcp://localhost:%SNDCPY_PORT%
goto :EOF

:error
echo Failed with error #%errorlevel%.
exit /b %errorlevel%


================================================
FILE: QtScrcpy/sndcpy/sndcpy.sh
================================================
#!/bin/bash

echo Begin Runing...
SNDCPY_PORT=28200
SNDCPY_APK=sndcpy.apk
ADB=./adb

serial=
if [[ $# -ge 2 ]]
then
    serial="-s $1"
    SNDCPY_PORT=$2
fi

echo "Waiting for device $1..."
$ADB $serial wait-for-device
echo "Find device $1"

sndcpy_installed=$($ADB $serial shell pm path com.rom1v.sndcpy)
if [[ $sndcpy_installed == "" ]]; then
    echo Install $SNDCPY_APK... 
    $ADB $serial uninstall com.rom1v.sndcpy || echo uninstall failed
    $ADB $serial install -t -r -g $SNDCPY_APK
    echo Install $SNDCPY_APK success
fi

echo Request PROJECT_MEDIA permission...
$ADB $serial shell appops set com.rom1v.sndcpy PROJECT_MEDIA allow

echo Forward port $SNDCPY_PORT...
$ADB $serial forward tcp:$SNDCPY_PORT localabstract:sndcpy

echo Start $SNDCPY_APK...
$ADB $serial shell am start com.rom1v.sndcpy/.MainActivity

while ((1))
do
    echo Waiting $SNDCPY_APK start...
    sleep 0.1
    sndcpy_started=$($ADB shell 'ps | grep com.rom1v.sndcpy')
    if [[ $sndcpy_started != "" ]]; then
        break
    fi
done

echo Ready playing...

================================================
FILE: QtScrcpy/ui/dialog.cpp
================================================
#include <QDebug>
#include <QFile>
#include <QFileDialog>
#include <QKeyEvent>
#include <QRandomGenerator>
#include <QTime>
#include <QTimer>

#include "config.h"
#include "dialog.h"
#include "ui_dialog.h"
#include "videoform.h"
#include "../groupcontroller/groupcontroller.h"

#ifdef Q_OS_WIN32
#include "../util/winutils.h"
#endif

QString s_keyMapPath = "";

const QString &getKeyMapPath()
{
    if (s_keyMapPath.isEmpty()) {
        s_keyMapPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_KEYMAP_PATH"));
        QFileInfo fileInfo(s_keyMapPath);
        if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {
            s_keyMapPath = QCoreApplication::applicationDirPath() + "/keymap";
        }
    }
    return s_keyMapPath;
}

Dialog::Dialog(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    initUI();

    updateBootConfig(true);

    on_useSingleModeCheck_clicked();
    on_updateDevice_clicked();

    connect(&m_autoUpdatetimer, &QTimer::timeout, this, &Dialog::on_updateDevice_clicked);
    if (ui->autoUpdatecheckBox->isChecked()) {
        m_autoUpdatetimer.start(5000);
    }

    connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {
        QString log = "";
        bool newLine = true;
        QStringList args = m_adb.arguments();

        switch (processResult) {
        case qsc::AdbProcess::AER_ERROR_START:
            break;
        case qsc::AdbProcess::AER_SUCCESS_START:
            log = "adb run";
            newLine = false;
            break;
        case qsc::AdbProcess::AER_ERROR_EXEC:
            //log = m_adb.getErrorOut();
            if (args.contains("ifconfig") && args.contains("wlan0")) {
                getIPbyIp();
            }
            break;
        case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:
            log = "adb not found";
            break;
        case qsc::AdbProcess::AER_SUCCESS_EXEC:
            //log = m_adb.getStdOut();
            if (args.contains("devices")) {
                QStringList devices = m_adb.getDevicesSerialFromStdOut();
                ui->serialBox->clear();
                ui->connectedPhoneList->clear();
                for (auto &item : devices) {
                    ui->serialBox->addItem(item);
                    ui->connectedPhoneList->addItem(Config::getInstance().getNickName(item) + "-" + item);
                }
            } else if (args.contains("show") && args.contains("wlan0")) {
                QString ip = m_adb.getDeviceIPFromStdOut();
                if (ip.isEmpty()) {
                    log = "ip not find, connect to wifi?";
                    break;
                }
                ui->deviceIpEdt->setEditText(ip);
            } else if (args.contains("ifconfig") && args.contains("wlan0")) {
                QString ip = m_adb.getDeviceIPFromStdOut();
                if (ip.isEmpty()) {
                    log = "ip not find, connect to wifi?";
                    break;
                }
                ui->deviceIpEdt->setEditText(ip);
            } else if (args.contains("ip -o a")) {
                QString ip = m_adb.getDeviceIPByIpFromStdOut();
                if (ip.isEmpty()) {
                    log = "ip not find, connect to wifi?";
                    break;
                }
                ui->deviceIpEdt->setEditText(ip);
            }
            break;
        }
        if (!log.isEmpty()) {
            outLog(log, newLine);
        }
    });

    m_hideIcon = new QSystemTrayIcon(this);
    m_hideIcon->setIcon(QIcon(":/image/tray/logo.png"));
    m_menu = new QMenu(this);
    m_quit = new QAction(this);
    m_showWindow = new QAction(this);
    m_showWindow->setText(tr("show"));
    m_quit->setText(tr("quit"));
    m_menu->addAction(m_showWindow);
    m_menu->addAction(m_quit);
    m_hideIcon->setContextMenu(m_menu);
    m_hideIcon->show();
    connect(m_showWindow, &QAction::triggered, this, &Dialog::show);
    connect(m_quit, &QAction::triggered, this, [this]() {
        m_hideIcon->hide();
        qApp->quit();
    });
    connect(m_hideIcon, &QSystemTrayIcon::activated, this, &Dialog::slotActivated);

    connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected, this, &Dialog::onDeviceConnected);
    connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected, this, &Dialog::onDeviceDisconnected);
}

Dialog::~Dialog()
{
    qDebug() << "~Dialog()";
    updateBootConfig(false);
    qsc::IDeviceManage::getInstance().disconnectAllDevice();
    delete ui;
}

void Dialog::initUI()
{
    setAttribute(Qt::WA_DeleteOnClose);
    //setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);

    setWindowTitle(Config::getInstance().getTitle());
#ifdef Q_OS_LINUX
    // Set window icon (inherits from application icon set in main.cpp)
    // If application icon was set, this will use it automatically
    if (!qApp->windowIcon().isNull()) {
        setWindowIcon(qApp->windowIcon());
    }
#endif

#ifdef Q_OS_WIN32
    WinUtils::setDarkBorderToWindow((HWND)this->winId(), true);
#endif

    ui->bitRateEdit->setValidator(new QIntValidator(1, 99999, this));

    ui->maxSizeBox->addItem("640");
    ui->maxSizeBox->addItem("720");
    ui->maxSizeBox->addItem("1080");
    ui->maxSizeBox->addItem("1280");
    ui->maxSizeBox->addItem("1920");
    ui->maxSizeBox->addItem(tr("original"));

    ui->formatBox->addItem("mp4");
    ui->formatBox->addItem("mkv");

    ui->lockOrientationBox->addItem(tr("no lock"));
    ui->lockOrientationBox->addItem("0");
    ui->lockOrientationBox->addItem("90");
    ui->lockOrientationBox->addItem("180");
    ui->lockOrientationBox->addItem("270");
    ui->lockOrientationBox->setCurrentIndex(0);

    // 加载IP历史记录
    loadIpHistory();

    // 加载端口历史记录
    loadPortHistory();

    // 为deviceIpEdt添加右键菜单
    if (ui->deviceIpEdt->lineEdit()) {
        ui->deviceIpEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(ui->deviceIpEdt->lineEdit(), &QWidget::customContextMenuRequested,
                this, &Dialog::showIpEditMenu);
    }
    
    // 为devicePortEdt添加右键菜单
    if (ui->devicePortEdt->lineEdit()) {
        ui->devicePortEdt->lineEdit()->setContextMenuPolicy(Qt::CustomContextMenu);
        connect(ui->devicePortEdt->lineEdit(), &QWidget::customContextMenuRequested,
                this, &Dialog::showPortEditMenu);
    }
}

void Dialog::updateBootConfig(bool toView)
{
    if (toView) {
        UserBootConfig config = Config::getInstance().getUserBootConfig();

        if (config.bitRate == 0) {
            ui->bitRateBox->setCurrentText("Mbps");
        } else if (config.bitRate % 1000000 == 0) {
            ui->bitRateEdit->setText(QString::number(config.bitRate / 1000000));
            ui->bitRateBox->setCurrentText("Mbps");
        } else {
            ui->bitRateEdit->setText(QString::number(config.bitRate / 1000));
            ui->bitRateBox->setCurrentText("Kbps");
        }

        ui->maxSizeBox->setCurrentIndex(config.maxSizeIndex);
        ui->formatBox->setCurrentIndex(config.recordFormatIndex);
        ui->recordPathEdt->setText(config.recordPath);
        ui->lockOrientationBox->setCurrentIndex(config.lockOrientationIndex);
        ui->framelessCheck->setChecked(config.framelessWindow);
        ui->recordScreenCheck->setChecked(config.recordScreen);
        ui->notDisplayCheck->setChecked(config.recordBackground);
        ui->useReverseCheck->setChecked(config.reverseConnect);
        ui->fpsCheck->setChecked(config.showFPS);
        ui->alwaysTopCheck->setChecked(config.windowOnTop);
        ui->closeScreenCheck->setChecked(config.autoOffScreen);
        ui->stayAwakeCheck->setChecked(config.keepAlive);
        ui->useSingleModeCheck->setChecked(config.simpleMode);
        ui->autoUpdatecheckBox->setChecked(config.autoUpdateDevice);
        ui->showToolbar->setChecked(config.showToolbar);
    } else {
        UserBootConfig config;

        config.bitRate = getBitRate();
        config.maxSizeIndex = ui->maxSizeBox->currentIndex();
        config.recordFormatIndex = ui->formatBox->currentIndex();
        config.recordPath = ui->recordPathEdt->text();
        config.lockOrientationIndex = ui->lockOrientationBox->currentIndex();
        config.recordScreen = ui->recordScreenCheck->isChecked();
        config.recordBackground = ui->notDisplayCheck->isChecked();
        config.reverseConnect = ui->useReverseCheck->isChecked();
        config.showFPS = ui->fpsCheck->isChecked();
        config.windowOnTop = ui->alwaysTopCheck->isChecked();
        config.autoOffScreen = ui->closeScreenCheck->isChecked();
        config.framelessWindow = ui->framelessCheck->isChecked();
        config.keepAlive = ui->stayAwakeCheck->isChecked();
        config.simpleMode = ui->useSingleModeCheck->isChecked();
        config.autoUpdateDevice = ui->autoUpdatecheckBox->isChecked();
        config.showToolbar = ui->showToolbar->isChecked();

        // 保存当前IP到历史记录
        QString currentIp = ui->deviceIpEdt->currentText().trimmed();
        if (!currentIp.isEmpty()) {
            saveIpHistory(currentIp);
        }

        Config::getInstance().setUserBootConfig(config);
    }
}

void Dialog::execAdbCmd()
{
    if (checkAdbRun()) {
        return;
    }
    QString cmd = ui->adbCommandEdt->text().trimmed();
    outLog("adb " + cmd, false);
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
    m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", Qt::SkipEmptyParts));
#else
    m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", QString::SkipEmptyParts));
#endif
}

void Dialog::delayMs(int ms)
{
    QTime dieTime = QTime::currentTime().addMSecs(ms);

    while (QTime::currentTime() < dieTime) {
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }
}

QString Dialog::getGameScript(const QString &fileName)
{
    if (fileName.isEmpty()) {
        return "";
    }

    QFile loadFile(getKeyMapPath() + "/" + fileName);
    if (!loadFile.open(QIODevice::ReadOnly)) {
        outLog("open file failed:" + fileName, true);
        return "";
    }

    QString ret = loadFile.readAll();
    loadFile.close();
    return ret;
}

void Dialog::slotActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason) {
    case QSystemTrayIcon::Trigger:
#ifdef Q_OS_WIN32
        this->show();
#endif
        break;
    default:
        break;
    }
}

void Dialog::closeEvent(QCloseEvent *event)
{
    this->hide();
    if (!Config::getInstance().getTrayMessageShown()) {
        Config::getInstance().setTrayMessageShown(true);
        m_hideIcon->showMessage(tr("Notice"),
                                tr("Hidden here!"),
                                QSystemTrayIcon::Information,
                                3000);
    }
    event->ignore();
}

void Dialog::on_updateDevice_clicked()
{
    if (checkAdbRun()) {
        return;
    }
    outLog("update devices...", false);
    m_adb.execute("", QStringList() << "devices");
}

void Dialog::on_startServerBtn_clicked()
{
    outLog("start server...", false);

    // this is ok that "original" toUshort is 0
    quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();
    qsc::DeviceParams params;
    params.serial = ui->serialBox->currentText().trimmed();
    params.maxSize = videoSize;
    params.bitRate = getBitRate();
    // on devices with Android >= 10, the capture frame rate can be limited
    params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
    params.closeScreen = ui->closeScreenCheck->isChecked();
    params.useReverse = ui->useReverseCheck->isChecked();
    params.display = !ui->notDisplayCheck->isChecked();
    params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();
    if (ui->lockOrientationBox->currentIndex() > 0) {
        params.captureOrientationLock = 1;
        params.captureOrientation = (ui->lockOrientationBox->currentIndex() - 1) * 90;
    }
    params.stayAwake = ui->stayAwakeCheck->isChecked();
    params.recordFile = ui->recordScreenCheck->isChecked();
    params.recordPath = ui->recordPathEdt->text().trimmed();
    params.recordFileFormat = ui->formatBox->currentText().trimmed();
    params.serverLocalPath = getServerPath();
    params.serverRemotePath = Config::getInstance().getServerPath();
    params.pushFilePath = Config::getInstance().getPushFilePath();
    params.gameScript = getGameScript(ui->gameBox->currentText());
    params.logLevel = Config::getInstance().getLogLevel();
    params.codecOptions = Config::getInstance().getCodecOptions();
    params.codecName = Config::getInstance().getCodecName();
    params.scid = QRandomGenerator::global()->bounded(1, 10000) & 0x7FFFFFFF;

    qsc::IDeviceManage::getInstance().connectDevice(params);
}

void Dialog::on_stopServerBtn_clicked()
{
    if (qsc::IDeviceManage::getInstance().disconnectDevice(ui->serialBox->currentText().trimmed())) {
        outLog("stop server");
    }
}

void Dialog::on_wirelessConnectBtn_clicked()
{
    if (checkAdbRun()) {
        return;
    }
    QString addr = ui->deviceIpEdt->currentText().trimmed();
    if (addr.isEmpty()) {
        outLog("error: device ip is null", false);
        return;
    }

    if (!ui->devicePortEdt->currentText().isEmpty()) {
        addr += ":";
        addr += ui->devicePortEdt->currentText().trimmed();
    } else if (!ui->devicePortEdt->lineEdit()->placeholderText().isEmpty()) {
        addr += ":";
        addr += ui->devicePortEdt->lineEdit()->placeholderText().trimmed();
    } else {
        outLog("error: device port is null", false);
        return;
    }

    // 保存IP历史记录 - 只保存IP部分,不包含端口
    QString ip = addr.split(":").first();
    if (!ip.isEmpty()) {
        saveIpHistory(ip);
    }
    
    // 保存端口历史记录
    QString port = addr.split(":").last();
    if (!port.isEmpty() && port != ip) {
        savePortHistory(port);
    }

    outLog("wireless connect...", false);
    QStringList adbArgs;
    adbArgs << "connect";
    adbArgs << addr;
    m_adb.execute("", adbArgs);
}

void Dialog::on_startAdbdBtn_clicked()
{
    if (checkAdbRun()) {
        return;
    }
    outLog("start devices adbd...", false);
    // adb tcpip 5555
    QStringList adbArgs;
    adbArgs << "tcpip";
    adbArgs << "5555";
    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}

void Dialog::outLog(const QString &log, bool newLine)
{
    // avoid sub thread update ui
    QString backLog = log;
    QTimer::singleShot(0, this, [this, backLog, newLine]() {
        ui->outEdit->append(backLog);
        if (newLine) {
            ui->outEdit->append("<br/>");
        }
    });
}

bool Dialog::filterLog(const QString &log)
{
    if (log.contains("app_proces")) {
        return true;
    }
    if (log.contains("Unable to set geometry")) {
        return true;
    }
    return false;
}

bool Dialog::checkAdbRun()
{
    if (m_adb.isRuning()) {
        outLog("wait for the end of the current command to run");
    }
    return m_adb.isRuning();
}

void Dialog::on_getIPBtn_clicked()
{
    if (checkAdbRun()) {
        return;
    }

    outLog("get ip...", false);
    // adb -s P7C0218510000537 shell ifconfig wlan0
    // or
    // adb -s P7C0218510000537 shell ip -f inet addr show wlan0
    QStringList adbArgs;
#if 0
    adbArgs << "shell";
    adbArgs << "ip";
    adbArgs << "-f";
    adbArgs << "inet";
    adbArgs << "addr";
    adbArgs << "show";
    adbArgs << "wlan0";
#else
    adbArgs << "shell";
    adbArgs << "ifconfig";
    adbArgs << "wlan0";
#endif
    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}

void Dialog::getIPbyIp()
{
    if (checkAdbRun()) {
        return;
    }

    QStringList adbArgs;
    adbArgs << "shell";
    adbArgs << "ip -o a";

    m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
}

void Dialog::onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size)
{
    Q_UNUSED(deviceName);
    if (!success) {
        return;
    }
    auto videoForm = new VideoForm(ui->framelessCheck->isChecked(), Config::getInstance().getSkin(), ui->showToolbar->isChecked());
    videoForm->setSerial(serial);

    qsc::IDeviceManage::getInstance().getDevice(serial)->setUserData(static_cast<void*>(videoForm));
    qsc::IDeviceManage::getInstance().getDevice(serial)->registerDeviceObserver(videoForm);


    videoForm->showFPS(ui->fpsCheck->isChecked());

    if (ui->alwaysTopCheck->isChecked()) {
        videoForm->staysOnTop();
    }

#ifndef Q_OS_WIN32
    // must be show before updateShowSize
    videoForm->show();
#endif
    QString name = Config::getInstance().getNickName(serial);
    if (name.isEmpty()) {
        name = Config::getInstance().getTitle();
    }
    videoForm->setWindowTitle(name + "-" + serial);
    videoForm->updateShowSize(size);

    bool deviceVer = size.height() > size.width();
    QRect rc = Config::getInstance().getRect(serial);
    bool rcVer = rc.height() > rc.width();
    // same width/height rate
    if (rc.isValid() && (deviceVer == rcVer)) {
        // mark: resize is for fix setGeometry magneticwidget bug
        videoForm->resize(rc.size());
        videoForm->setGeometry(rc);
    }

#ifdef Q_OS_WIN32
    // windows是show太早可以看到resize的过程
    QTimer::singleShot(200, videoForm, [videoForm](){videoForm->show();});
#endif

    GroupController::instance().addDevice(serial);
}

void Dialog::onDeviceDisconnected(QString serial)
{
    GroupController::instance().removeDevice(serial);
    auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
    if (!device) {
        return;
    }
    auto data = device->getUserData();
    if (data) {
        VideoForm* vf = static_cast<VideoForm*>(data);
        qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);
        vf->close();
        vf->deleteLater();
    }
}

void Dialog::on_wirelessDisConnectBtn_clicked()
{
    if (checkAdbRun()) {
        return;
    }
    QString addr = ui->deviceIpEdt->currentText().trimmed();
    outLog("wireless disconnect...", false);
    QStringList adbArgs;
    adbArgs << "disconnect";
    adbArgs << addr;
    m_adb.execute("", adbArgs);
}

void Dialog::on_selectRecordPathBtn_clicked()
{
    QFileDialog::Options options = QFileDialog::DontResolveSymlinks | QFileDialog::ShowDirsOnly;
    QString directory = QFileDialog::getExistingDirectory(this, tr("select path"), "", options);
    ui->recordPathEdt->setText(directory);
}

void Dialog::on_recordPathEdt_textChanged(const QString &arg1)
{
    ui->recordPathEdt->setToolTip(arg1.trimmed());
    ui->notDisplayCheck->setCheckable(!arg1.trimmed().isEmpty());
}

void Dialog::on_adbCommandBtn_clicked()
{
    execAdbCmd();
}

void Dialog::on_stopAdbBtn_clicked()
{
    m_adb.kill();
}

void Dialog::on_clearOut_clicked()
{
    ui->outEdit->clear();
}

void Dialog::on_stopAllServerBtn_clicked()
{
    qsc::IDeviceManage::getInstance().disconnectAllDevice();
}

void Dialog::on_refreshGameScriptBtn_clicked()
{
    ui->gameBox->clear();
    QDir dir(getKeyMapPath());
    if (!dir.exists()) {
        outLog("keymap directory not find", true);
        return;
    }
    dir.setFilter(QDir::Files | QDir::NoSymLinks);
    QFileInfoList list = dir.entryInfoList();
    QFileInfo fileInfo;
    int size = list.size();
    for (int i = 0; i < size; ++i) {
        fileInfo = list.at(i);
        ui->gameBox->addItem(fileInfo.fileName());
    }
}

void Dialog::on_applyScriptBtn_clicked()
{
    auto curSerial = ui->serialBox->currentText().trimmed();
    auto device = qsc::IDeviceManage::getInstance().getDevice(curSerial);
    if (!device) {
        return;
    }

    device->updateScript(getGameScript(ui->gameBox->currentText()));
}

void Dialog::on_recordScreenCheck_clicked(bool checked)
{
    if (!checked) {
        return;
    }

    QString fileDir(ui->recordPathEdt->text().trimmed());
    if (fileDir.isEmpty()) {
        qWarning() << "please select record save path!!!";
        ui->recordScreenCheck->setChecked(false);
    }
}

void Dialog::on_usbConnectBtn_clicked()
{
    on_stopAllServerBtn_clicked();
    delayMs(200);
    on_updateDevice_clicked();
    delayMs(200);

    int firstUsbDevice = findDeviceFromeSerialBox(false);
    if (-1 == firstUsbDevice) {
        qWarning() << "No use device is found!";
        return;
    }
    ui->serialBox->setCurrentIndex(firstUsbDevice);

    on_startServerBtn_clicked();
}

int Dialog::findDeviceFromeSerialBox(bool wifi)
{
    QString regStr = "\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\:([0-9]|[1-9]\\d|[1-9]\\d{2}|[1-9]\\d{3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])\\b";
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
    QRegExp regIP(regStr);
#else
    QRegularExpression regIP(regStr);
#endif
    for (int i = 0; i < ui->serialBox->count(); ++i) {
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
        bool isWifi = regIP.exactMatch(ui->serialBox->itemText(i));
#else
        bool isWifi = regIP.match(ui->serialBox->itemText(i)).hasMatch();
#endif
        bool found = wifi ? isWifi : !isWifi;
        if (found) {
            return i;
        }
    }

    return -1;
}

void Dialog::on_wifiConnectBtn_clicked()
{
    on_stopAllServerBtn_clicked();
    delayMs(200);

    on_updateDevice_clicked();
    delayMs(200);

    int firstUsbDevice = findDeviceFromeSerialBox(false);
    if (-1 == firstUsbDevice) {
        qWarning() << "No use device is found!";
        return;
    }
    ui->serialBox->setCurrentIndex(firstUsbDevice);

    on_getIPBtn_clicked();
    delayMs(200);

    on_startAdbdBtn_clicked();
    delayMs(1000);

    on_wirelessConnectBtn_clicked();
    delayMs(2000);

    on_updateDevice_clicked();
    delayMs(200);

    int firstWifiDevice = findDeviceFromeSerialBox(true);
    if (-1 == firstWifiDevice) {
        qWarning() << "No wifi device is found!";
        return;
    }
    ui->serialBox->setCurrentIndex(firstWifiDevice);

    on_startServerBtn_clicked();
}

void Dialog::on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item)
{
    Q_UNUSED(item);
    ui->serialBox->setCurrentIndex(ui->connectedPhoneList->currentRow());
    on_startServerBtn_clicked();
}

void Dialog::on_updateNameBtn_clicked()
{
    if (ui->serialBox->count() != 0) {
        if (ui->userNameEdt->text().isEmpty()) {
            Config::getInstance().setNickName(ui->serialBox->currentText(), "Phone");
        } else {
            Config::getInstance().setNickName(ui->serialBox->currentText(), ui->userNameEdt->text());
        }

        on_updateDevice_clicked();

        qDebug() << "Update OK!";
    } else {
        qWarning() << "No device is connected!";
    }
}

void Dialog::on_useSingleModeCheck_clicked()
{
    if (ui->useSingleModeCheck->isChecked()) {
        ui->rightWidget->hide();
    } else {
        ui->rightWidget->show();
    }

    adjustSize();
}

void Dialog::on_serialBox_currentIndexChanged(const QString &arg1)
{
    ui->userNameEdt->setText(Config::getInstance().getNickName(arg1));
}

quint32 Dialog::getBitRate()
{
    return ui->bitRateEdit->text().trimmed().toUInt() *
            (ui->bitRateBox->currentText() == QString("Mbps") ? 1000000 : 1000);
}

const QString &Dialog::getServerPath()
{
    static QString serverPath;
    if (serverPath.isEmpty()) {
        serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
        QFileInfo fileInfo(serverPath);
        if (serverPath.isEmpty() || !fileInfo.isFile()) {
            serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
        }
    }
    return serverPath;
}

void Dialog::on_startAudioBtn_clicked()
{
    if (ui->serialBox->count() == 0) {
        qWarning() << "No device is connected!";
        return;
    }

    m_audioOutput.start(ui->serialBox->currentText(), 28200);
}

void Dialog::on_stopAudioBtn_clicked()
{
    m_audioOutput.stop();
}

void Dialog::on_installSndcpyBtn_clicked()
{
    if (ui->serialBox->count() == 0) {
        qWarning() << "No device is connected!";
        return;
    }
    m_audioOutput.installonly(ui->serialBox->currentText(), 28200);
}

void Dialog::on_autoUpdatecheckBox_toggled(bool checked)
{
    if (checked) {
        m_autoUpdatetimer.start(5000);
    } else {
        m_autoUpdatetimer.stop();
    }
}

void Dialog::loadIpHistory()
{
    QStringList ipList = Config::getInstance().getIpHistory();
    ui->deviceIpEdt->clear();
    ui->deviceIpEdt->addItems(ipList);
    ui->deviceIpEdt->setContentsMargins(0, 0, 0, 0);

    if (ui->deviceIpEdt->lineEdit()) {
        ui->deviceIpEdt->lineEdit()->setMaxLength(128);
        ui->deviceIpEdt->lineEdit()->setPlaceholderText("192.168.0.1");
    }
}

void Dialog::saveIpHistory(const QString &ip)
{
    if (ip.isEmpty()) {
        return;
    }
    
    Config::getInstance().saveIpHistory(ip);
    
    // 更新ComboBox
    loadIpHistory();
    ui->deviceIpEdt->setCurrentText(ip);
}

void Dialog::showIpEditMenu(const QPoint &pos)
{
    QMenu *menu = ui->deviceIpEdt->lineEdit()->createStandardContextMenu();
    menu->addSeparator();
    
    QAction *clearHistoryAction = new QAction(tr("Clear History"), menu);
    connect(clearHistoryAction, &QAction::triggered, this, [this]() {
        Config::getInstance().clearIpHistory();
        loadIpHistory();
    });
    
    menu->addAction(clearHistoryAction);
    menu->exec(ui->deviceIpEdt->lineEdit()->mapToGlobal(pos));
    delete menu;
}

void Dialog::loadPortHistory()
{
    QStringList portList = Config::getInstance().getPortHistory();
    ui->devicePortEdt->clear();
    ui->devicePortEdt->addItems(portList);
    ui->devicePortEdt->setContentsMargins(0, 0, 0, 0);

    if (ui->devicePortEdt->lineEdit()) {
        ui->devicePortEdt->lineEdit()->setMaxLength(6);
        ui->devicePortEdt->lineEdit()->setPlaceholderText("5555");
    }
}

void Dialog::savePortHistory(const QString &port)
{
    if (port.isEmpty()) {
        return;
    }
    
    Config::getInstance().savePortHistory(port);
    
    // 更新ComboBox
    loadPortHistory();
    ui->devicePortEdt->setCurrentText(port);
}

void Dialog::showPortEditMenu(const QPoint &pos)
{
    QMenu *menu = ui->devicePortEdt->lineEdit()->createStandardContextMenu();
    menu->addSeparator();
    
    QAction *clearHistoryAction = new QAction(tr("Clear History"), menu);
    connect(clearHistoryAction, &QAction::triggered, this, [this]() {
        Config::getInstance().clearPortHistory();
        loadPortHistory();
    });
    
    menu->addAction(clearHistoryAction);
    menu->exec(ui->devicePortEdt->lineEdit()->mapToGlobal(pos));
    delete menu;
}


================================================
FILE: QtScrcpy/ui/dialog.h
================================================
#ifndef DIALOG_H
#define DIALOG_H

#include <QWidget>
#include <QPointer>
#include <QMessageBox>
#include <QMenu>
#include <QSystemTrayIcon>
#include <QListWidget>
#include <QTimer>


#include "adbprocess.h"
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
#include "audio/audiooutput.h"

namespace Ui
{
    class Widget;
}

class QYUVOpenGLWidget;
class Dialog : public QWidget
{
    Q_OBJECT

public:
    explicit Dialog(QWidget *parent = 0);
    ~Dialog();

    void outLog(const QString &log, bool newLine = true);
    bool filterLog(const QString &log);
    void getIPbyIp();

private slots:
    void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size);
    void onDeviceDisconnected(QString serial);

    void on_updateDevice_clicked();
    void on_startServerBtn_clicked();
    void on_stopServerBtn_clicked();
    void on_wirelessConnectBtn_clicked();
    void on_startAdbdBtn_clicked();
    void on_getIPBtn_clicked();
    void on_wirelessDisConnectBtn_clicked();
    void on_selectRecordPathBtn_clicked();
    void on_recordPathEdt_textChanged(const QString &arg1);
    void on_adbCommandBtn_clicked();
    void on_stopAdbBtn_clicked();
    void on_clearOut_clicked();
    void on_stopAllServerBtn_clicked();
    void on_refreshGameScriptBtn_clicked();
    void on_applyScriptBtn_clicked();
    void on_recordScreenCheck_clicked(bool checked);
    void on_usbConnectBtn_clicked();
    void on_wifiConnectBtn_clicked();
    void on_connectedPhoneList_itemDoubleClicked(QListWidgetItem *item);
    void on_updateNameBtn_clicked();
    void on_useSingleModeCheck_clicked();
    void on_serialBox_currentIndexChanged(const QString &arg1);

    void on_startAudioBtn_clicked();

    void on_stopAudioBtn_clicked();

    void on_installSndcpyBtn_clicked();

    void on_autoUpdatecheckBox_toggled(bool checked);

    void showIpEditMenu(const QPoint &pos);

private:
    bool checkAdbRun();
    void initUI();
    void updateBootConfig(bool toView = true);
    void execAdbCmd();
    void delayMs(int ms);
    QString getGameScript(const QString &fileName);
    void slotActivated(QSystemTrayIcon::ActivationReason reason);
    int findDeviceFromeSerialBox(bool wifi);
    quint32 getBitRate();
    const QString &getServerPath();
    void loadIpHistory();
    void saveIpHistory(const QString &ip);
    void loadPortHistory();
    void savePortHistory(const QString &port);

    void showPortEditMenu(const QPoint &pos);

protected:
    void closeEvent(QCloseEvent *event);

private:
    Ui::Widget *ui;
    qsc::AdbProcess m_adb;
    QSystemTrayIcon *m_hideIcon;
    QMenu *m_menu;
    QAction *m_showWindow;
    QAction *m_quit;
    AudioOutput m_audioOutput;
    QTimer m_autoUpdatetimer;
};

#endif // DIALOG_H


================================================
FILE: QtScrcpy/ui/dialog.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Widget</class>
 <widget class="QWidget" name="Widget">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1293</width>
    <height>502</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string notr="true">QtScrcpy</string>
  </property>
  <layout class="QHBoxLayout" name="horizontalLayout_11">
   <property name="leftMargin">
    <number>0</number>
   </property>
   <property name="topMargin">
    <number>0</number>
   </property>
   <property name="rightMargin">
    <number>0</number>
   </property>
   <property name="bottomMargin">
    <number>0</number>
   </property>
   <item>
    <widget class="QWidget" name="leftWidget" native="true">
     <property name="sizePolicy">
      <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_5">
      <item>
       <widget class="QCheckBox" name="useSingleModeCheck">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="styleSheet">
         <string notr="true"/>
        </property>
        <property name="text">
         <string>Use Simple Mode</string>
        </property>
        <property name="checked">
         <bool>false</bool>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QGroupBox" name="simpleGroupBox">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="title">
         <string>Simple Mode</string>
        </property>
        <property name="checkable">
         <bool>false</bool>
        </property>
        <layout class="QVBoxLayout" name="verticalLayout_4">
         <item>
          <layout class="QHBoxLayout" name="horizontalLayout_9">
           <item>
            <widget class="QPushButton" name="wifiConnectBtn">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>WIFI Connect</string>
             </property>
            </widget>
           </item>
           <item>
            <widget class="QPushButton" name="usbConnectBtn">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>USB Connect</string>
             </property>
            </widget>
           </item>
          </layout>
         </item>
         <item>
          <layout class="QHBoxLayout" name="horizontalLayout_13">
           <property name="topMargin">
            <number>0</number>
           </property>
           <item>
            <widget class="QLabel" name="label_10">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>Double click to connect:</string>
             </property>
            </widget>
           </item>
           <item>
            <widget class="QCheckBox" name="autoUpdatecheckBox">
             <property name="sizePolicy">
              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
             <property name="text">
              <string>auto update</string>
             </property>
             <property name="checked">
              <bool>true</bool>
             </property>
            </widget>
           </item>
          </layout>
         </item>
         <item>
          <widget class="QListWidget" name="connectedPhoneList">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
          </widget>
         </item>
        </layout>
       </widget>
      </item>
      <item>
       <widget class="QGroupBox" name="adbGroupBox">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="title">
         <string notr="true">adb</string>
        </property>
        <layout class="QHBoxLayout" name="horizontalLayout_2">
         <property name="spacing">
          <number>3</number>
         </property>
         <property name="leftMargin">
          <number>5</number>
         </property>
         <property name="topMargin">
          <number>5</number>
         </property>
         <property name="rightMargin">
          <number>5</number>
         </property>
         <property name="bottomMargin">
          <number>5</number>
         </property>
         <item>
          <widget class="QLabel" name="label_7">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>adb command:</string>
           </property>
           <property name="buddy">
            <cstring>adbCommandEdt</cstring>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QLineEdit" name="adbCommandEdt">
           <property name="sizePolicy">
            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string notr="true">devices</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="adbCommandBtn">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>execute</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="stopAdbBtn">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>terminate</string>
           </property>
          </widget>
         </item>
         <item>
          <widget class="QPushButton" name="clearOut">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <property name="text">
            <string>clear</string>
           </property>
          </widget>
         </item>
        </layout>
       </widget>
      </item>
      <item>
       <widget class="QTextEdit" name="outEdit">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="focusPolicy">
         <enum>Qt::NoFocus</enum>
        </property>
        <property name="documentTitle">
         <string/>
        </property>
        <property name="readOnly">
         <bool>true</bool>
        </property>
       </widget>
      </item>
     </layout>
    </widget>
   </item>
   <item>
    <widget class="QWidget" name="rightWidget" native="true">
     <property name="sizePolicy">
      <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
       <horstretch>0</horstretch>
       <verstretch>0</verstretch>
      </sizepolicy>
     </property>
     <layout class="QVBoxLayout" name="verticalLayout_6">
      <item>
       <widget class="QGroupBox" name="configGroupBox">
        <property name="sizePolicy">
         <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
          <horstretch>0</horstretch>
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
        <property name="title">
         <string>Start Config</string>
        </property>
        <layout class="QVBoxLayout" name="verticalLayout_3">
         <property name="spacing">
          <number>3</number>
         </property>
         <property name="leftMargin">
          <number>5</number>
         </property>
         <property name="topMargin">
          <number>5</number>
         </property>
         <property name="rightMargin">
          <number>5</number>
         </property>
         <property name="bottomMargin">
          <number>5</number>
         </property>
         <item>
          <widget class="QWidget" name="configWidget1" native="true">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_5">
            <property name="leftMargin">
             <number>0</number>
            </property>
            <property name="topMargin">
             <number>0</number>
            </property>
            <property name="rightMargin">
             <number>0</number>
            </property>
            <property name="bottomMargin">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_3">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>bit rate:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLineEdit" name="bitRateEdit">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string notr="true">2</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QComboBox" name="bitRateBox">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="toolTip">
               <string/>
              </property>
              <property name="currentText">
               <string notr="true">Mbps</string>
              </property>
              <item>
               <property name="text">
                <string notr="true">Mbps</string>
               </property>
              </item>
              <item>
               <property name="text">
                <string notr="true">Kbps</string>
               </property>
              </item>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="label_4">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>max size:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QComboBox" name="maxSizeBox">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="toolTip">
               <string/>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QWidget" name="configWidget5" native="true">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_7">
            <property name="leftMargin">
             <number>0</number>
            </property>
            <property name="topMargin">
             <number>0</number>
            </property>
            <property name="rightMargin">
             <number>0</number>
            </property>
            <property name="bottomMargin">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_6">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>record format:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QComboBox" name="formatBox">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLabel" name="label_8">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>lock orientation:</string>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QComboBox" name="lockOrientationBox">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QWidget" name="configWidget2" native="true">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_6">
            <property name="leftMargin">
             <number>0</number>
            </property>
            <property name="topMargin">
             <number>0</number>
            </property>
            <property name="rightMargin">
             <number>0</number>
            </property>
            <property name="bottomMargin">
             <number>0</number>
            </property>
            <item>
             <widget class="QLabel" name="label_5">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>record save path:</string>
              </property>
              <property name="buddy">
               <cstring>recordPathEdt</cstring>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QLineEdit" name="recordPathEdt">
              <property name="sizePolicy">
               <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="readOnly">
               <bool>true</bool>
              </property>
             </widget>
            </item>
            <item>
             <widget class="QPushButton" name="selectRecordPathBtn">
              <property name="sizePolicy">
               <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
                <horstretch>0</horstretch>
                <verstretch>0</verstretch>
               </sizepolicy>
              </property>
              <property name="text">
               <string>select path</string>
              </property>
              <property name="autoDefault">
               <bool>false</bool>
              </property>
             </widget>
            </item>
           </layout>
          </widget>
         </item>
         <item>
          <widget class="QWidget" name="configWidget4" native="true">
           <property name="sizePolicy">
            <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
             <horstretch>0</horstretch>
             <verstretch>0</verstretch>
            </sizepolicy>
           </property>
           <layout class="QHBoxLayout" name="horizontalLayout_8">
            <property name="leftMargin">
             <number>0</number>
            </property>
            <property name="topMargin">
             <number>0</number>
            </property>
            <property name="rightMargin">
             <number>0</number>
            </property>
            <property name="bottomMargin">
             <number>0</number>
            </property>
            <item>
             <widget class="QComboBox" name="gameBox">
              <property name="sizePolicy">
               <size
Download .txt
gitextract_v8zjkv6h/

├── .clang-format
├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── macos.yml
│       ├── ubuntu.yml
│       └── windows.yml
├── .gitignore
├── .gitmodules
├── CMakeLists.txt
├── LICENSE
├── QtScrcpy/
│   ├── CMakeLists.txt
│   ├── appversion
│   ├── audio/
│   │   ├── audiooutput.cpp
│   │   └── audiooutput.h
│   ├── clang-format-all.sh
│   ├── fontawesome/
│   │   ├── iconhelper.cpp
│   │   └── iconhelper.h
│   ├── groupcontroller/
│   │   ├── groupcontroller.cpp
│   │   └── groupcontroller.h
│   ├── main.cpp
│   ├── render/
│   │   ├── qyuvopenglwidget.cpp
│   │   └── qyuvopenglwidget.h
│   ├── res/
│   │   ├── Info_Mac.plist.in
│   │   ├── QtScrcpy.icns
│   │   ├── QtScrcpy.rc
│   │   ├── i18n/
│   │   │   ├── CMakeLists.txt
│   │   │   ├── en_US.qm
│   │   │   ├── en_US.ts
│   │   │   ├── ja_JP.qm
│   │   │   ├── ja_JP.ts
│   │   │   ├── ko_KR.qm
│   │   │   ├── ko_KR.ts
│   │   │   ├── zh_CN.qm
│   │   │   └── zh_CN.ts
│   │   ├── qss/
│   │   │   └── psblack.css
│   │   └── res.qrc
│   ├── sndcpy/
│   │   ├── sndcpy.apk
│   │   ├── sndcpy.bat
│   │   └── sndcpy.sh
│   ├── ui/
│   │   ├── dialog.cpp
│   │   ├── dialog.h
│   │   ├── dialog.ui
│   │   ├── toolform.cpp
│   │   ├── toolform.h
│   │   ├── toolform.ui
│   │   ├── videoform.cpp
│   │   ├── videoform.h
│   │   └── videoform.ui
│   ├── uibase/
│   │   ├── keepratiowidget.cpp
│   │   ├── keepratiowidget.h
│   │   ├── magneticwidget.cpp
│   │   └── magneticwidget.h
│   └── util/
│       ├── config.cpp
│       ├── config.h
│       ├── mousetap/
│       │   ├── cocoamousetap.h
│       │   ├── cocoamousetap.mm
│       │   ├── mousetap.cpp
│       │   ├── mousetap.h
│       │   ├── winmousetap.cpp
│       │   ├── winmousetap.h
│       │   ├── xmousetap.cpp
│       │   └── xmousetap.h
│       ├── path.h
│       ├── path.mm
│       ├── winutils.cpp
│       └── winutils.h
├── README.md
├── README_zh.md
├── backup/
│   └── myconfig.sh
├── ci/
│   ├── generate-version.py
│   ├── linux/
│   │   ├── build_for_linux.sh
│   │   ├── package_appimage.sh
│   │   └── publish_for_ubuntu.sh.todo
│   ├── lrelease.sh
│   ├── lupdate.sh
│   ├── mac/
│   │   ├── build_for_mac.sh
│   │   ├── package/
│   │   │   ├── dmg-settings.json
│   │   │   ├── package.py
│   │   │   └── requirements.txt
│   │   ├── package_for_mac.sh
│   │   └── publish_for_mac.sh
│   └── win/
│       ├── build_for_win.bat
│       └── publish_for_win.bat
├── config/
│   └── config.ini
├── docs/
│   ├── DEVELOP.md
│   ├── FAQ.md
│   ├── KeyMapDes.md
│   ├── KeyMapDes_zh.md
│   └── TODO.md
└── keymap/
    ├── FRAG.json
    ├── gameforpeace.json
    ├── identityv.json
    ├── test.json
    └── tiktok.json
Download .txt
SYMBOL INDEX (56 symbols across 25 files)

FILE: QtScrcpy/audio/audiooutput.h
  function class (line 12) | class AudioOutput : public QObject

FILE: QtScrcpy/fontawesome/iconhelper.h
  function class (line 12) | class IconHelper : public QObject

FILE: QtScrcpy/groupcontroller/groupcontroller.cpp
  function QSize (line 21) | QSize GroupController::getFrameSize(const QString &serial)
  function GroupController (line 31) | GroupController &GroupController::instance()

FILE: QtScrcpy/main.cpp
  function main (line 26) | int main(int argc, char *argv[])
  function installTranslator (line 161) | void installTranslator()
  function QtMsgType (line 196) | QtMsgType covertLogLevel(const QString &logLevel)
  function myMessageOutput (line 221) | void myMessageOutput(QtMsgType type, const QMessageLogContext &context, ...

FILE: QtScrcpy/render/qyuvopenglwidget.cpp
  function QSize (line 107) | QSize QYUVOpenGLWidget::minimumSizeHint() const
  function QSize (line 112) | QSize QYUVOpenGLWidget::sizeHint() const
  function QSize (line 127) | const QSize &QYUVOpenGLWidget::frameSize()

FILE: QtScrcpy/ui/dialog.cpp
  function QString (line 21) | const QString &getKeyMapPath()
  function QString (line 279) | QString Dialog::getGameScript(const QString &fileName)
  function quint32 (line 768) | quint32 Dialog::getBitRate()
  function QString (line 774) | const QString &Dialog::getServerPath()

FILE: QtScrcpy/ui/dialog.h
  function namespace (line 17) | namespace Ui
  function class (line 23) | class Dialog : public QWidget

FILE: QtScrcpy/ui/toolform.h
  function namespace (line 10) | namespace Ui
  function class (line 16) | class ToolForm : public MagneticWidget

FILE: QtScrcpy/ui/videoform.cpp
  function QRect (line 86) | QRect VideoForm::getGrabCursorRect()
  function QSize (line 122) | const QSize &VideoForm::frameSize()
  function QRect (line 367) | QRect VideoForm::getScreenRect()
  function QMargins (line 405) | QMargins VideoForm::getMargins(bool vertical)

FILE: QtScrcpy/ui/videoform.h
  function namespace (line 9) | namespace Ui

FILE: QtScrcpy/uibase/keepratiowidget.cpp
  function QSize (line 28) | const QSize KeepRatioWidget::goodSize()

FILE: QtScrcpy/uibase/keepratiowidget.h
  function class (line 7) | class KeepRatioWidget : public QWidget

FILE: QtScrcpy/uibase/magneticwidget.h
  function Q_OBJECT (line 14) | Q_OBJECT

FILE: QtScrcpy/util/config.cpp
  function Config (line 135) | Config &Config::getInstance()
  function QString (line 141) | const QString &Config::getConfigPath()
  function UserBootConfig (line 185) | UserBootConfig Config::getUserBootConfig()
  function QRect (line 237) | QRect Config::getRect(const QString &serial)
  function QString (line 257) | QString Config::getNickName(const QString &serial)
  function QString (line 304) | QString Config::getPushFilePath()
  function QString (line 313) | QString Config::getServerPath()
  function QString (line 322) | QString Config::getAdbPath()
  function QString (line 331) | QString Config::getLogLevel()
  function QString (line 340) | QString Config::getCodecOptions()
  function QString (line 349) | QString Config::getCodecName()
  function QStringList (line 358) | QStringList Config::getConnectedGroups()
  function QString (line 368) | QString Config::getLanguage()
  function QString (line 377) | QString Config::getTitle()
  function QStringList (line 405) | QStringList Config::getIpHistory()
  function QStringList (line 437) | QStringList Config::getPortHistory()

FILE: QtScrcpy/util/config.h
  type UserBootConfig (line 8) | struct UserBootConfig
  function class (line 29) | class Config : public QObject

FILE: QtScrcpy/util/mousetap/cocoamousetap.h
  type MouseEventTapData (line 8) | struct MouseEventTapData

FILE: QtScrcpy/util/mousetap/mousetap.cpp
  function MouseTap (line 15) | MouseTap *MouseTap::getInstance()

FILE: QtScrcpy/util/mousetap/mousetap.h
  function class (line 6) | class MouseTap

FILE: QtScrcpy/util/mousetap/winmousetap.h
  function class (line 6) | class WinMouseTap : public MouseTap

FILE: QtScrcpy/util/mousetap/xmousetap.cpp
  function find_grab_window_recursive (line 23) | static void find_grab_window_recursive(xcb_connection_t *dpy, xcb_window...

FILE: QtScrcpy/util/mousetap/xmousetap.h
  function class (line 6) | class XMouseTap : public MouseTap

FILE: QtScrcpy/util/path.h
  function class (line 3) | class Path {

FILE: QtScrcpy/util/winutils.cpp
  type WORD (line 8) | enum : WORD

FILE: QtScrcpy/util/winutils.h
  function class (line 7) | class WinUtils

FILE: ci/mac/package/package.py
  function console_print (line 13) | def console_print(msg):
  function generate_dmg_info (line 17) | def generate_dmg_info():
Condensed preview — 93 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (424K chars).
[
  {
    "path": ".clang-format",
    "chars": 5843,
    "preview": "---\n# 语言: None, Cpp, Java, JavaScript, ObjC, Proto, TableGen, TextProto\nLanguage:        Cpp\n# BasedOnStyle:  WebKit\n# 访"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 634,
    "preview": "\n# These are supported funding model platforms\n\ngithub: barry-ran\npatreon: # Replace with a single Patreon username\nopen"
  },
  {
    "path": ".github/workflows/macos.yml",
    "chars": 3568,
    "preview": "name: MacOS\non: \n  push:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/macos.ym"
  },
  {
    "path": ".github/workflows/ubuntu.yml",
    "chars": 4870,
    "preview": "name: Ubuntu\non: \n  push:\n    paths:\n      - 'QtScrcpy/**'\n      - '!QtScrcpy/res/**'\n      - '.github/workflows/ubuntu."
  },
  {
    "path": ".github/workflows/windows.yml",
    "chars": 4649,
    "preview": "name: Windows\n# 触发规则详解 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#on\non: \n  push:\n "
  },
  {
    "path": ".gitignore",
    "chars": 297,
    "preview": "/output\n*.user\n/QtScrcpy/*.user\n/server/.gradle\n/server/.idea\n/server/build\n/server/gradle/wrapper/gradle-wrapper.jar\n/s"
  },
  {
    "path": ".gitmodules",
    "chars": 115,
    "preview": "[submodule \"QtScrcpy/QtScrcpyCore\"]\n\tpath = QtScrcpy/QtScrcpyCore\n\turl = git@github.com:barry-ran/QtScrcpyCore.git\n"
  },
  {
    "path": "CMakeLists.txt",
    "chars": 91,
    "preview": "cmake_minimum_required(VERSION 3.19 FATAL_ERROR)\nproject(all)\n\nadd_subdirectory(QtScrcpy)\n"
  },
  {
    "path": "LICENSE",
    "chars": 11374,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "QtScrcpy/CMakeLists.txt",
    "chars": 12107,
    "preview": "# For VS2019 and Xcode 12+ support.\ncmake_minimum_required(VERSION 3.19 FATAL_ERROR)\n\n#\n# Global config\n#\n\n# QC is \"Qt C"
  },
  {
    "path": "QtScrcpy/appversion",
    "chars": 6,
    "preview": "0.0.0\n"
  },
  {
    "path": "QtScrcpy/audio/audiooutput.cpp",
    "chars": 6499,
    "preview": "#include <QAudioOutput>\n#include <QCoreApplication>\n#include <QElapsedTimer>\n#include <QHostAddress>\n#include <QTcpSocke"
  },
  {
    "path": "QtScrcpy/audio/audiooutput.h",
    "chars": 1001,
    "preview": "#ifndef AUDIOOUTPUT_H\n#define AUDIOOUTPUT_H\n\n#include <QThread>\n#include <QProcess>\n#include <QPointer>\n#include <QVecto"
  },
  {
    "path": "QtScrcpy/clang-format-all.sh",
    "chars": 2402,
    "preview": "#!/bin/bash\n#\n# clang-format-all: a tool to run clang-format on an entire project\n# Copyright (C) 2016 Evan Klitzke <eva"
  },
  {
    "path": "QtScrcpy/fontawesome/iconhelper.cpp",
    "chars": 629,
    "preview": "#include \"iconhelper.h\"\r\n\r\nIconHelper *IconHelper::_instance = 0;\r\nIconHelper::IconHelper(QObject *) : QObject(qApp)\r\n{\r"
  },
  {
    "path": "QtScrcpy/fontawesome/iconhelper.h",
    "chars": 787,
    "preview": "#ifndef ICONHELPER_H\r\n#define ICONHELPER_H\r\n\r\n#include <QApplication>\r\n#include <QFont>\r\n#include <QFontDatabase>\r\n#incl"
  },
  {
    "path": "QtScrcpy/groupcontroller/groupcontroller.cpp",
    "chars": 9982,
    "preview": "#include <QPointer>\n\n#include \"groupcontroller.h\"\n#include \"videoform.h\"\n\nGroupController::GroupController(QObject *pare"
  },
  {
    "path": "QtScrcpy/groupcontroller/groupcontroller.h",
    "chars": 1823,
    "preview": "#ifndef GROUPCONTROLLER_H\n#define GROUPCONTROLLER_H\n\n#include <QObject>\n#include <QVector>\n\n#include \"QtScrcpyCore.h\"\n\nc"
  },
  {
    "path": "QtScrcpy/main.cpp",
    "chars": 9168,
    "preview": "#include <QApplication>\n#include <QDebug>\n#include <QFile>\n#ifdef Q_OS_LINUX\n#include <QFileInfo>\n#include <QIcon>\n#end"
  },
  {
    "path": "QtScrcpy/render/qyuvopenglwidget.cpp",
    "chars": 8140,
    "preview": "#include <QCoreApplication>\n#include <QOpenGLTexture>\n#include <QSurfaceFormat>\n\n#include \"qyuvopenglwidget.h\"\n\n// 存储顶点坐"
  },
  {
    "path": "QtScrcpy/render/qyuvopenglwidget.h",
    "chars": 1343,
    "preview": "#ifndef QYUVOPENGLWIDGET_H\n#define QYUVOPENGLWIDGET_H\n#include <QOpenGLBuffer>\n#include <QOpenGLFunctions>\n#include <QOp"
  },
  {
    "path": "QtScrcpy/res/Info_Mac.plist.in",
    "chars": 1296,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "QtScrcpy/res/QtScrcpy.rc",
    "chars": 1016,
    "preview": "#include \"winres.h\"\r\n\r\nIDI_ICON1       ICON      \"QtScrcpy.ico\"\r\n// GB2312编码的话,在中文系统上打包FileDescription可以显示中文\r\n// 在github"
  },
  {
    "path": "QtScrcpy/res/i18n/CMakeLists.txt",
    "chars": 2352,
    "preview": "# 声明ts文件\nset(QC_TS_FILES \n    ${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts \n    ${CMAKE_CURRENT_SOURCE_DIR}/en_US.ts\n    ${CMAK"
  },
  {
    "path": "QtScrcpy/res/i18n/en_US.ts",
    "chars": 9740,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"en_US\">\n<context>\n    <name>Dialog</nam"
  },
  {
    "path": "QtScrcpy/res/i18n/ja_JP.ts",
    "chars": 9254,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!DOCTYPE TS []>\r\n<TS version=\"2.1\" language=\"ja_JP\">\r\n  <context>\r\n    <name>D"
  },
  {
    "path": "QtScrcpy/res/i18n/ko_KR.ts",
    "chars": 9188,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"ko\">\n<context>\n    <name>Dialog</name>\n"
  },
  {
    "path": "QtScrcpy/res/i18n/zh_CN.ts",
    "chars": 8962,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE TS>\n<TS version=\"2.1\" language=\"zh_CN\">\n<context>\n    <name>Dialog</nam"
  },
  {
    "path": "QtScrcpy/res/qss/psblack.css",
    "chars": 15207,
    "preview": "QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;}\n\nQWidget[form=\"true\"],QLabel[frameShape=\"1\"]{\nborder:1px soli"
  },
  {
    "path": "QtScrcpy/res/res.qrc",
    "chars": 1385,
    "preview": "<RCC>\n    <qresource prefix=\"/\">\n        <file>font/fontawesome-webfont.ttf</file>\n        <file>image/videoform/phone-h"
  },
  {
    "path": "QtScrcpy/sndcpy/sndcpy.bat",
    "chars": 1541,
    "preview": "@echo off\n\necho Begin Runing...\nset SNDCPY_PORT=28200\nset SNDCPY_APK=sndcpy.apk\nset ADB=adb.exe\n\nif not \"%1\"==\"\" (\n    s"
  },
  {
    "path": "QtScrcpy/sndcpy/sndcpy.sh",
    "chars": 1041,
    "preview": "#!/bin/bash\n\necho Begin Runing...\nSNDCPY_PORT=28200\nSNDCPY_APK=sndcpy.apk\nADB=./adb\n\nserial=\nif [[ $# -ge 2 ]]\nthen\n    "
  },
  {
    "path": "QtScrcpy/ui/dialog.cpp",
    "chars": 26731,
    "preview": "#include <QDebug>\n#include <QFile>\n#include <QFileDialog>\n#include <QKeyEvent>\n#include <QRandomGenerator>\n#include <QT"
  },
  {
    "path": "QtScrcpy/ui/dialog.h",
    "chars": 2771,
    "preview": "#ifndef DIALOG_H\n#define DIALOG_H\n\n#include <QWidget>\n#include <QPointer>\n#include <QMessageBox>\n#include <QMenu>\n#incl"
  },
  {
    "path": "QtScrcpy/ui/dialog.ui",
    "chars": 41361,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>Widget</class>\n <widget class=\"QWidget\" name=\"Widget\">"
  },
  {
    "path": "QtScrcpy/ui/toolform.cpp",
    "chars": 5950,
    "preview": "#include <QDebug>\n#include <QHideEvent>\n#include <QMouseEvent>\n#include <QShowEvent>\n\n#include \"iconhelper.h\"\n#include \""
  },
  {
    "path": "QtScrcpy/ui/toolform.h",
    "chars": 1428,
    "preview": "#ifndef TOOLFORM_H\n#define TOOLFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScrcp"
  },
  {
    "path": "QtScrcpy/ui/toolform.ui",
    "chars": 4335,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>ToolForm</class>\n <widget class=\"QWidget\" name=\"ToolFo"
  },
  {
    "path": "QtScrcpy/ui/videoform.cpp",
    "chars": 25228,
    "preview": "// #include <QDesktopWidget>\n#include <QFileInfo>\n#include <QLabel>\n#include <QMessageBox>\n#include <QMimeData>\n#include"
  },
  {
    "path": "QtScrcpy/ui/videoform.h",
    "chars": 2736,
    "preview": "#ifndef VIDEOFORM_H\n#define VIDEOFORM_H\n\n#include <QPointer>\n#include <QWidget>\n\n#include \"../QtScrcpyCore/include/QtScr"
  },
  {
    "path": "QtScrcpy/ui/videoform.ui",
    "chars": 1359,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>videoForm</class>\n <widget class=\"QWidget\" name=\"video"
  },
  {
    "path": "QtScrcpy/uibase/keepratiowidget.cpp",
    "chars": 1560,
    "preview": "#include <QResizeEvent>\n#include <cmath>\n\n#include \"keepratiowidget.h\"\n\nKeepRatioWidget::KeepRatioWidget(QWidget *parent"
  },
  {
    "path": "QtScrcpy/uibase/keepratiowidget.h",
    "chars": 571,
    "preview": "#ifndef KEEPRATIOWIDGET_H\n#define KEEPRATIOWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\nclass KeepRatioWidget : pub"
  },
  {
    "path": "QtScrcpy/uibase/magneticwidget.cpp",
    "chars": 5992,
    "preview": "#include <QDebug>\n#include <QMoveEvent>\n#include <QStyle>\n\n#include \"magneticwidget.h\"\n\nMagneticWidget::MagneticWidget(Q"
  },
  {
    "path": "QtScrcpy/uibase/magneticwidget.h",
    "chars": 1500,
    "preview": "#ifndef MAGNETICWIDGET_H\n#define MAGNETICWIDGET_H\n\n#include <QPointer>\n#include <QWidget>\n\n/*\n * a magnetic widget\n * wi"
  },
  {
    "path": "QtScrcpy/util/config.cpp",
    "chars": 13940,
    "preview": "#include <QCoreApplication>\n#include <QFileInfo>\n#include <QSettings>\n#include <QDebug>\n\n#include \"config.h\"\n#ifdef Q_O"
  },
  {
    "path": "QtScrcpy/util/config.h",
    "chars": 2121,
    "preview": "#ifndef CONFIG_H\n#define CONFIG_H\n\n#include <QObject>\n#include <QPointer>\n#include <QRect>\n\nstruct UserBootConfig\n{\n   "
  },
  {
    "path": "QtScrcpy/util/mousetap/cocoamousetap.h",
    "chars": 612,
    "preview": "#ifndef COCOAMOUSETAP_H\n#define COCOAMOUSETAP_H\n#include <QSemaphore>\n#include <QThread>\n\n#include \"mousetap.h\"\n\nstruct "
  },
  {
    "path": "QtScrcpy/util/mousetap/cocoamousetap.mm",
    "chars": 7369,
    "preview": "#import <Cocoa/Cocoa.h>\n#include <QDebug>\n\n#include \"cocoamousetap.h\"\n\nstatic const CGEventMask movementEventsMask =\n   "
  },
  {
    "path": "QtScrcpy/util/mousetap/mousetap.cpp",
    "chars": 531,
    "preview": "#include <QtGlobal>\n\n#include \"mousetap.h\"\n#ifdef Q_OS_WIN32\n#include \"winmousetap.h\"\n#endif\n#ifdef Q_OS_OSX\n#include \"c"
  },
  {
    "path": "QtScrcpy/util/mousetap/mousetap.h",
    "chars": 425,
    "preview": "#ifndef MOUSETAP_H\n#define MOUSETAP_H\n#include <QRect>\n\nclass QWidget;\nclass MouseTap\n{\npublic:\n    static MouseTap *get"
  },
  {
    "path": "QtScrcpy/util/mousetap/winmousetap.cpp",
    "chars": 646,
    "preview": "#include <QDebug>\n#include <QWidget>\n#include <Windows.h>\n\n#include \"winmousetap.h\"\n\nWinMouseTap::WinMouseTap() {}\n\nWinM"
  },
  {
    "path": "QtScrcpy/util/mousetap/winmousetap.h",
    "chars": 331,
    "preview": "#ifndef WINMOUSETAP_H\n#define WINMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass WinMouseTap : public MouseTap\n{\npublic:\n    Wi"
  },
  {
    "path": "QtScrcpy/util/mousetap/xmousetap.cpp",
    "chars": 2877,
    "preview": "#include <QtGlobal>\n\n#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))\n#include <QtX11Extras/QX11Info>\n#else\n#include <QtGui/"
  },
  {
    "path": "QtScrcpy/util/mousetap/xmousetap.h",
    "chars": 319,
    "preview": "#ifndef XMOUSETAP_H\n#define XMOUSETAP_H\n\n#include \"mousetap.h\"\n\nclass XMouseTap : public MouseTap\n{\npublic:\n    XMouseTa"
  },
  {
    "path": "QtScrcpy/util/path.h",
    "chars": 79,
    "preview": "#pragma once\n\nclass Path {\npublic:\n    static const char* GetCurrentPath();\n};\n"
  },
  {
    "path": "QtScrcpy/util/path.mm",
    "chars": 143,
    "preview": "#include \"path.h\"\n\n#import <Cocoa/Cocoa.h>\n\nconst char* Path::GetCurrentPath() {\n    return [[[NSBundle mainBundle] bund"
  },
  {
    "path": "QtScrcpy/util/winutils.cpp",
    "chars": 848,
    "preview": "#include <QDebug>\n#include <Windows.h>\n#include <dwmapi.h>\n#pragma comment(lib, \"dwmapi\")\n\n#include \"winutils.h\"\n\nenum :"
  },
  {
    "path": "QtScrcpy/util/winutils.h",
    "chars": 241,
    "preview": "#ifndef WINUTILS_H\n#define WINUTILS_H\n\n#include <QApplication>\n#include <Windows.h>\n\nclass WinUtils\n{\npublic:\n    WinUti"
  },
  {
    "path": "README.md",
    "chars": 18425,
    "preview": "# QtScrcpy \n\n[![Financial Contributors to Open Collective](https://opencollective.com/QtScrcpy/all/badge.svg?label=finan"
  },
  {
    "path": "README_zh.md",
    "chars": 9272,
    "preview": "# QtScrcpy\n\n![Windows](https://github.com/barry-ran/QtScrcpy/workflows/Windows/badge.svg)\n![MacOS](https://github.com/ba"
  },
  {
    "path": "backup/myconfig.sh",
    "chars": 223,
    "preview": "./configure --disable-everything --disable-x86asm --prefix=../ffmpeg_build \\\n\t--enable-shared --enable-static \\\n\t--enabl"
  },
  {
    "path": "ci/generate-version.py",
    "chars": 472,
    "preview": "import sys\nimport os\n\nif __name__ == '__main__':\n    p = os.popen('git rev-list --tags --max-count=1')\n    commit = p.re"
  },
  {
    "path": "ci/linux/build_for_linux.sh",
    "chars": 1916,
    "preview": "echo ---------------------------------------------------------------\necho Check \\& Set Environment Variables\necho ------"
  },
  {
    "path": "ci/linux/package_appimage.sh",
    "chars": 14442,
    "preview": "#!/bin/bash\n\necho \"Package AppImage\"\n\nbuild_mode=\"$1\"\nif [[ $build_mode != \"Release\" && $build_mode != \"Debug\" && $build"
  },
  {
    "path": "ci/linux/publish_for_ubuntu.sh.todo",
    "chars": 2688,
    "preview": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------"
  },
  {
    "path": "ci/lrelease.sh",
    "chars": 169,
    "preview": "# https://doc.qt.io/qt-5/linguist-manager.html#lrelease\n# lrelease -help\nlrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcp"
  },
  {
    "path": "ci/lupdate.sh",
    "chars": 240,
    "preview": "# https://doc.qt.io/qt-5/linguist-manager.html#lupdate\n# lupdate -help\n# export PATH=/D/Qt/5.15.2/msvc2019/bin:$PATH\nlup"
  },
  {
    "path": "ci/mac/build_for_mac.sh",
    "chars": 2409,
    "preview": "\necho\necho\necho ---------------------------------------------------------------\necho check ENV\necho --------------------"
  },
  {
    "path": "ci/mac/package/dmg-settings.json",
    "chars": 447,
    "preview": "{\"icon-size\": 120, \"format\": \"UDZO\", \"title\": \"QtScrcpy\", \"compression-level\": 9, \"window\": {\"position\": {\"y\": 200, \"x\":"
  },
  {
    "path": "ci/mac/package/package.py",
    "chars": 1612,
    "preview": "import dmgbuild\nimport os\nimport json\nimport sys\n\ncurrent_file_path = os.path.dirname(os.path.realpath(__file__))\ndmg_se"
  },
  {
    "path": "ci/mac/package/requirements.txt",
    "chars": 15,
    "preview": "dmgbuild==1.4.2"
  },
  {
    "path": "ci/mac/package_for_mac.sh",
    "chars": 782,
    "preview": "# 获取绝对路径,保证其他目录执行此脚本依然正确\n{\ncd $(dirname \"$0\")\nscript_path=$(pwd)\ncd -\n} &> /dev/null # disable output\n# 设置当前目录,cd的目录影响接下"
  },
  {
    "path": "ci/mac/publish_for_mac.sh",
    "chars": 3163,
    "preview": "echo\necho\necho ---------------------------------------------------------------\necho check ENV\necho ---------------------"
  },
  {
    "path": "ci/win/build_for_win.bat",
    "chars": 2721,
    "preview": "@echo off\r\n\r\necho=\r\necho=\r\necho ---------------------------------------------------------------\r\necho check ENV\r\necho --"
  },
  {
    "path": "ci/win/publish_for_win.bat",
    "chars": 4346,
    "preview": "@echo off\n\necho=\necho=\necho ---------------------------------------------------------------\necho check ENV\necho --------"
  },
  {
    "path": "config/config.ini",
    "chars": 696,
    "preview": "[common]\n# 语言 Auto=自动,zh_CN=简体中文,en_US=English\nLanguage=Auto\n# 窗口标题\nWindowTitle=QtScrcpy\n# 推送到安卓设备的文件保存路径(必须以/结尾)\nPushF"
  },
  {
    "path": "docs/DEVELOP.md",
    "chars": 12880,
    "preview": "# scrcpy for developers\n\n## Overview\n\nThis application is composed of two parts:\n - the server (`scrcpy-server`), to be "
  },
  {
    "path": "docs/FAQ.md",
    "chars": 1198,
    "preview": "# Frequently Asked Questions\n一些经常问的问题\n\n如果在此文档没有解决你的问题,描述你的问题,截图软件控制台中打印的日志,一起发到QQ群里提问。\n\n# adb问题\n## ADB版本之间的冲突\n```\nadb se"
  },
  {
    "path": "docs/KeyMapDes.md",
    "chars": 6001,
    "preview": "# Custom key mapping instructions\n\nThe key map file is in json format, and the new key map file needs to be placed in th"
  },
  {
    "path": "docs/KeyMapDes_zh.md",
    "chars": 2666,
    "preview": "# 自定义按键映射说明\n\n按键映射文件为json格式,新增自己的按键映射文件需要放在keymap目录中才可以被QtScrcpy识别。\n\n按键映射文件的具体编写格式下面会介绍,也可以参考自带的按键映射文件。\n\n## 按键映射脚本格式说明\n\n#"
  },
  {
    "path": "docs/TODO.md",
    "chars": 1457,
    "preview": "# TODO\n## 低优先级\n- text转换 https://github.com/Genymobile/scrcpy/commit/c916af0984f72a60301d13fa8ef9a85112f54202?tdsourcetag"
  },
  {
    "path": "keymap/FRAG.json",
    "chars": 1969,
    "preview": "{\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.5,\n\t\t\t\"y\""
  },
  {
    "path": "keymap/gameforpeace.json",
    "chars": 4724,
    "preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"mouseMoveMap\": {\n\t\t\"startPos\": {\n\t\t\t\"x\": 0.57,\n\t\t\t\"y\": 0.26\n\t\t},\n\t\t\"speedRatioX\": 3.2"
  },
  {
    "path": "keymap/identityv.json",
    "chars": 3521,
    "preview": "{\n\t\"comment\":\"https://doc.qt.io/qt-5/qt.html#Key-enum\",\n\t\"old-switchKey\": \"Key_QuoteLeft\",\n\t\"switchKey\": \"RightButton\",\n"
  },
  {
    "path": "keymap/test.json",
    "chars": 502,
    "preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"测试一键多点\",\n\t\t\t\"type\": \"KMT_CLICK_MULTI\",\n\t\t\t\"key\": \""
  },
  {
    "path": "keymap/tiktok.json",
    "chars": 883,
    "preview": "{\n\t\"switchKey\": \"Key_QuoteLeft\",\n\t\"keyMapNodes\": [\n\t\t{\n\t\t\t\"comment\": \"暂停/继续\",\n\t\t\t\"type\": \"KMT_CLICK\",\n\t\t\t\"key\": \"Key_Spa"
  }
]

// ... and 6 more files (download for full content)

About this extraction

This page contains the full source code of the barry-ran/QtScrcpy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 93 files (388.8 KB), approximately 113.3k tokens, and a symbol index with 56 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!