mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-04-20 19:44:59 +00:00
Compare commits
No commits in common. "dev" and "v1.4.0" have entirely different histories.
309 changed files with 42277 additions and 5666 deletions
49
.github/workflows/macos.yml
vendored
49
.github/workflows/macos.yml
vendored
|
@ -13,23 +13,12 @@ on:
|
|||
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
|
||||
runs-on: macos-latest
|
||||
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
|
||||
qt-ver: [5.12.6]
|
||||
qt-arch-install: [clang_64]
|
||||
clang-arch: [x64]
|
||||
env:
|
||||
target-name: QtScrcpy
|
||||
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
|
||||
|
@ -37,35 +26,25 @@ jobs:
|
|||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v1
|
||||
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
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2.6.2
|
||||
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
|
||||
- uses: actions/checkout@v1
|
||||
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 }}
|
||||
fetch-depth: 1
|
||||
# 编译
|
||||
- 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 }}
|
||||
ci/mac/build_for_mac.sh release
|
||||
# 获取ref最后一个/后的内容
|
||||
- name: Get the version
|
||||
shell: bash
|
||||
|
@ -77,17 +56,17 @@ jobs:
|
|||
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 }}
|
||||
publish_name: ${{ env.target-name }}-${{ env.plantform-des }}-${{ matrix.clang-arch }}-${{ steps.get-version.outputs.version }}
|
||||
run: |
|
||||
ci/mac/publish_for_mac.sh ../build ${{ matrix.build-arch }}
|
||||
ci/mac/publish_for_mac.sh ../build
|
||||
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
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: ${{ steps.package.outputs.package-name }}.zip
|
||||
path: ci/build/${{ steps.package.outputs.package-name }}.dmg
|
||||
path: ci/build/${{ steps.package.outputs.package-name }}.app
|
||||
# Upload to release
|
||||
- name: Upload Release
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
|
|
61
.github/workflows/ubuntu.yml
vendored
61
.github/workflows/ubuntu.yml
vendored
|
@ -1,11 +1,11 @@
|
|||
name: Ubuntu
|
||||
# Qt官方没有linux平台的x86包
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'QtScrcpy/**'
|
||||
- '!QtScrcpy/res/**'
|
||||
- '.github/workflows/ubuntu.yml'
|
||||
- 'ci/linux/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'QtScrcpy/**'
|
||||
|
@ -17,8 +17,8 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04]
|
||||
qt-ver: [5.15.2]
|
||||
os: [ubuntu-16.04,ubuntu-18.04]
|
||||
qt-ver: [5.12.6]
|
||||
qt-arch-install: [gcc_64]
|
||||
gcc-arch: [x64]
|
||||
env:
|
||||
|
@ -26,56 +26,25 @@ jobs:
|
|||
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
|
||||
plantform-des: ubuntu
|
||||
steps:
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v4.1.1
|
||||
with:
|
||||
version: ${{ matrix.qt-ver }}
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
- name: Cache Qt
|
||||
id: cache-qt
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ env.qt-install-path }}/${{ matrix.qt-arch-install }}
|
||||
key: ${{ runner.os }}/${{ matrix.qt-ver }}/${{ matrix.qt-arch-install }}
|
||||
- name: Install GL library
|
||||
- name: Install Qt
|
||||
uses: jurplel/install-qt-action@v2.6.2
|
||||
with:
|
||||
version: ${{ matrix.qt-ver }}
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
- name: Ubuntu install GL library
|
||||
run: sudo apt-get install -y libglew-dev libglfw3-dev
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'true'
|
||||
ssh-key: ${{ secrets.BOT_SSH_KEY }}
|
||||
- name: Build RelWithDebInfo
|
||||
fetch-depth: 1
|
||||
- name: Build Ubuntu
|
||||
env:
|
||||
ENV_QT_PATH: ${{ env.qt-install-path }}
|
||||
run: |
|
||||
ci/linux/build_for_linux.sh "RelWithDebInfo"
|
||||
- name: Upload RelWithDebInfo
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-RelWithDebInfo
|
||||
path: output/x64/RelWithDebInfo/*
|
||||
- name: Build Release
|
||||
env:
|
||||
ENV_QT_PATH: ${{ env.qt-install-path }}
|
||||
run: |
|
||||
ci/linux/build_for_linux.sh "Release"
|
||||
- name: Upload Release
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}-Release
|
||||
path: output/x64/Release/*
|
||||
- name: Install the zip utility
|
||||
run: |
|
||||
sudo apt install zip -y
|
||||
- name: Zip the Artifacts
|
||||
run: |
|
||||
zip -r QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip output/x64/Release
|
||||
- name: Upload to Releases
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
uses: svenstaro/upload-release-action@2.3.0
|
||||
with:
|
||||
file: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip
|
||||
asset_name: QtScrcpy-${{ matrix.os }}-${{ matrix.qt-arch-install }}.zip
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
python ci/generate-version.py
|
||||
ci/linux/build_for_ubuntu.sh release
|
||||
|
|
38
.github/workflows/windows.yml
vendored
38
.github/workflows/windows.yml
vendored
|
@ -6,55 +6,51 @@ on:
|
|||
- '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 2019
|
||||
# windows server 2019安装的是vs2019,windows server 2016安装的是vs2017
|
||||
# windows-latest目前是windows server 2019,选择2016是2016安装的是vs2017
|
||||
# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2016
|
||||
|
||||
# 矩阵配置 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-ver: [5.12.6]
|
||||
qt-arch: [win64_msvc2017_64, win32_msvc2017]
|
||||
# 配置qt-arch的额外设置msvc-arch,qt-arch-install
|
||||
include:
|
||||
- qt-arch: win64_msvc2019_64
|
||||
- qt-arch: win64_msvc2017_64
|
||||
msvc-arch: x64
|
||||
qt-arch-install: msvc2019_64
|
||||
- qt-arch: win32_msvc2019
|
||||
qt-arch-install: msvc2017_64
|
||||
- qt-arch: win32_msvc2017
|
||||
msvc-arch: x86
|
||||
qt-arch-install: msvc2019
|
||||
qt-arch-install: msvc2017
|
||||
# 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\2019\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
|
||||
vcinstall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC'
|
||||
vcvarsall-path: 'C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvarsall.bat'
|
||||
qt-install-path: ${{ github.workspace }}/${{ matrix.qt-ver }}
|
||||
plantform-des: win
|
||||
# 步骤
|
||||
steps:
|
||||
- name: Cache Qt
|
||||
id: cache-qt
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v1
|
||||
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
|
||||
uses: jurplel/install-qt-action@v2.6.2
|
||||
with:
|
||||
# Version of Qt to install
|
||||
version: ${{ matrix.qt-ver }}
|
||||
|
@ -64,11 +60,9 @@ jobs:
|
|||
arch: ${{ matrix.qt-arch }}
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
# 拉取代码
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'true'
|
||||
ssh-key: ${{ secrets.BOT_SSH_KEY }}
|
||||
fetch-depth: 1
|
||||
# 编译msvc
|
||||
- name: Build MSVC
|
||||
# shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
|
||||
|
@ -78,7 +72,7 @@ jobs:
|
|||
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 }}
|
||||
call "ci\win\build_for_win.bat" release ${{ matrix.msvc-arch }}
|
||||
# 获取ref最后一个/后的内容
|
||||
- name: Get the version
|
||||
shell: bash
|
||||
|
@ -89,8 +83,6 @@ jobs:
|
|||
- 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: |
|
||||
|
@ -100,7 +92,7 @@ jobs:
|
|||
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
|
||||
- uses: actions/upload-artifact@v1
|
||||
with:
|
||||
name: ${{ steps.package.outputs.package-name }}.zip
|
||||
path: ci\build\${{ steps.package.outputs.package-name }}
|
||||
|
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -12,6 +12,4 @@
|
|||
/build/
|
||||
build-*
|
||||
*.DS_Store
|
||||
userdata.ini
|
||||
Info_Mac.plist
|
||||
/ci/build_temp
|
||||
userdata.ini
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
|||
[submodule "QtScrcpy/QtScrcpyCore"]
|
||||
path = QtScrcpy/QtScrcpyCore
|
||||
url = git@github.com:barry-ran/QtScrcpyCore.git
|
|
@ -1,4 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
project(all)
|
||||
|
||||
add_subdirectory(QtScrcpy)
|
3
LICENSE
3
LICENSE
|
@ -186,8 +186,7 @@
|
|||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (C) 2019 Rankun
|
||||
Copyright (C) 2019-2025 Rankun
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
|
|
@ -1,382 +0,0 @@
|
|||
# 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}")
|
||||
|
||||
# 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})
|
||||
|
||||
#
|
||||
# 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
|
||||
)
|
219
QtScrcpy/QtScrcpy.pro
Normal file
219
QtScrcpy/QtScrcpy.pro
Normal file
|
@ -0,0 +1,219 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2018-10-07T12:36:10
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT += core gui
|
||||
QT += network
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TARGET = QtScrcpy
|
||||
TEMPLATE = app
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||
# depend on your compiler). Please consult the documentation of the
|
||||
# deprecated API in order to know how to port your code away from it.
|
||||
DEFINES += QT_DEPRECATED_WARNINGS
|
||||
|
||||
# You can also make your code fail to compile if you use deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
msvc{
|
||||
QMAKE_CFLAGS += -source-charset:utf-8
|
||||
QMAKE_CXXFLAGS += -source-charset:utf-8
|
||||
}
|
||||
|
||||
# warning as error
|
||||
#4566 https://github.com/Chuyu-Team/VC-LTL/issues/27
|
||||
*g++*: QMAKE_CXXFLAGS += -Werror
|
||||
*msvc*: QMAKE_CXXFLAGS += /WX /wd4566
|
||||
|
||||
# run a server debugger and wait for a client to be attached
|
||||
# DEFINES += SERVER_DEBUGGER
|
||||
# select the debugger method ('old' for Android < 9, 'new' for Android >= 9)
|
||||
# DEFINES += SERVER_DEBUGGER_METHOD_NEW
|
||||
|
||||
# 源码
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
dialog.cpp
|
||||
|
||||
HEADERS += \
|
||||
dialog.h
|
||||
|
||||
FORMS += \
|
||||
dialog.ui
|
||||
|
||||
# 子工程
|
||||
include ($$PWD/common/common.pri)
|
||||
include ($$PWD/adb/adb.pri)
|
||||
include ($$PWD/uibase/uibase.pri)
|
||||
include ($$PWD/fontawesome/fontawesome.pri)
|
||||
include ($$PWD/util/util.pri)
|
||||
include ($$PWD/device/device.pri)
|
||||
include ($$PWD/devicemanage/devicemanage.pri)
|
||||
|
||||
# 附加包含路径
|
||||
INCLUDEPATH += \
|
||||
$$PWD/common \
|
||||
$$PWD/adb \
|
||||
$$PWD/uibase \
|
||||
$$PWD/util \
|
||||
$$PWD/device \
|
||||
$$PWD/devicemanage \
|
||||
$$PWD/fontawesome
|
||||
|
||||
# 如果变量没有定义
|
||||
# !defined(TEST_VAR, var) {
|
||||
# message("test")
|
||||
# }
|
||||
|
||||
# 从文件读取版本号
|
||||
CAT_VERSION = $$cat($$PWD/version)
|
||||
# 拆分出版本号
|
||||
VERSION_MAJOR = $$section(CAT_VERSION, ., 0, 0)
|
||||
VERSION_MINOR = $$section(CAT_VERSION, ., 1, 1)
|
||||
VERSION_PATCH = $$section(CAT_VERSION, ., 2, 2)
|
||||
message("version:" $${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH})
|
||||
|
||||
# qmake变量的方式定义版本号
|
||||
VERSION = $${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH}
|
||||
|
||||
# ***********************************************************
|
||||
# Win平台下配置
|
||||
# ***********************************************************
|
||||
win32 {
|
||||
# 通过rc的方式的话,VERSION变量rc中获取不到,定义为宏方便rc中使用
|
||||
DEFINES += VERSION_MAJOR=$${VERSION_MAJOR}
|
||||
DEFINES += VERSION_MINOR=$${VERSION_MINOR}
|
||||
DEFINES += VERSION_PATCH=$${VERSION_PATCH}
|
||||
DEFINES += VERSION_RC_STR=\\\"$${VERSION_MAJOR}.$${VERSION_MINOR}.$${VERSION_PATCH}\\\"
|
||||
|
||||
contains(QT_ARCH, x86_64) {
|
||||
message("x64")
|
||||
# 输出目录
|
||||
CONFIG(debug, debug|release) {
|
||||
DESTDIR = $$PWD/../output/win/x64/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/win/x64/release
|
||||
}
|
||||
|
||||
# 依赖模块
|
||||
LIBS += \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavformat \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavcodec \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x64 -lavutil \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x64 -lswscale
|
||||
|
||||
WIN_FFMPEG_SRC = $$PWD/../third_party/ffmpeg/bin/x64/*.dll
|
||||
} else {
|
||||
message("x86")
|
||||
# 输出目录
|
||||
CONFIG(debug, debug|release) {
|
||||
DESTDIR = $$PWD/../output/win/x86/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/win/x86/release
|
||||
}
|
||||
|
||||
# 依赖模块
|
||||
LIBS += \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavformat \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavcodec \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x86 -lavutil \
|
||||
-L$$PWD/../third_party/ffmpeg/lib/x86 -lswscale
|
||||
|
||||
WIN_FFMPEG_SRC = $$PWD/../third_party/ffmpeg/bin/x86/*.dll
|
||||
}
|
||||
|
||||
# 复制依赖库
|
||||
WIN_DST = $$DESTDIR
|
||||
|
||||
WIN_FFMPEG_SRC ~= s,/,\\,g
|
||||
WIN_DST ~= s,/,\\,g
|
||||
|
||||
QMAKE_POST_LINK += $$quote($$QMAKE_COPY $$WIN_FFMPEG_SRC $$WIN_DST$$escape_expand(\n\t))
|
||||
|
||||
# windows rc file
|
||||
RC_FILE = $$PWD/res/QtScrcpy.rc
|
||||
}
|
||||
|
||||
# ***********************************************************
|
||||
# Mac平台下配置
|
||||
# ***********************************************************
|
||||
macos {
|
||||
# 输出目录
|
||||
CONFIG(debug, debug|release) {
|
||||
DESTDIR = $$PWD/../output/mac/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/mac/release
|
||||
}
|
||||
|
||||
# 依赖模块
|
||||
LIBS += \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavformat.58 \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavcodec.58 \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavutil.56 \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lswscale.5
|
||||
|
||||
# mac bundle file
|
||||
APP_SCRCPY_SERVER.files = $$files($$PWD/../third_party/scrcpy-server)
|
||||
APP_SCRCPY_SERVER.path = Contents/MacOS
|
||||
QMAKE_BUNDLE_DATA += APP_SCRCPY_SERVER
|
||||
|
||||
APP_ADB.files = $$files($$PWD/../third_party/adb/mac/adb)
|
||||
APP_ADB.path = Contents/MacOS
|
||||
QMAKE_BUNDLE_DATA += APP_ADB
|
||||
|
||||
APP_FFMPEG.files = $$files($$PWD/../third_party/ffmpeg/lib/*.dylib)
|
||||
APP_FFMPEG.path = Contents/MacOS
|
||||
QMAKE_BUNDLE_DATA += APP_FFMPEG
|
||||
|
||||
APP_CONFIG.files = $$files($$PWD/../config/config.ini)
|
||||
APP_CONFIG.path = Contents/MacOS/config
|
||||
QMAKE_BUNDLE_DATA += APP_CONFIG
|
||||
|
||||
# mac application icon
|
||||
ICON = $$PWD/res/QtScrcpy.icns
|
||||
QMAKE_INFO_PLIST = $$PWD/res/Info_Mac.plist
|
||||
|
||||
# 定义目标命令(修改版本号字段)
|
||||
plistupdate.commands = /usr/libexec/PlistBuddy -c \"Set :CFBundleShortVersionString $$VERSION\" \
|
||||
-c \"Set :CFBundleVersion $$VERSION\" \
|
||||
$$DESTDIR/$${TARGET}.app/Contents/Info.plist
|
||||
|
||||
# 增加额外目标
|
||||
QMAKE_EXTRA_TARGETS += plistupdate
|
||||
# 设置为前置依赖
|
||||
PRE_TARGETDEPS += plistupdate
|
||||
}
|
||||
|
||||
# ***********************************************************
|
||||
# Linux平台下配置
|
||||
# ***********************************************************
|
||||
linux {
|
||||
# 输出目录
|
||||
CONFIG(debug, debug|release) {
|
||||
DESTDIR = $$PWD/../output/linux/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/linux/release
|
||||
}
|
||||
|
||||
# 依赖模块
|
||||
LIBS += \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavformat \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavcodec \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lavutil \
|
||||
-L$$PWD/../third_party/ffmpeg/lib -lswscale
|
||||
|
||||
# linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
|
||||
}
|
||||
|
||||
# message("test")
|
||||
|
||||
RESOURCES += \
|
||||
res/res.qrc
|
||||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 19e1ba8fb5c59c5a85c3c6a79967fab4c84739c7
|
5
QtScrcpy/adb/adb.pri
Normal file
5
QtScrcpy/adb/adb.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/adbprocess.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/adbprocess.cpp
|
236
QtScrcpy/adb/adbprocess.cpp
Normal file
236
QtScrcpy/adb/adbprocess.cpp
Normal file
|
@ -0,0 +1,236 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
||||
#include "adbprocess.h"
|
||||
#include "config.h"
|
||||
|
||||
QString AdbProcess::s_adbPath = "";
|
||||
|
||||
AdbProcess::AdbProcess(QObject *parent) : QProcess(parent)
|
||||
{
|
||||
initSignals();
|
||||
}
|
||||
|
||||
AdbProcess::~AdbProcess()
|
||||
{
|
||||
if (isRuning()) {
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
const QString &AdbProcess::getAdbPath()
|
||||
{
|
||||
if (s_adbPath.isEmpty()) {
|
||||
s_adbPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_ADB_PATH"));
|
||||
QFileInfo fileInfo(s_adbPath);
|
||||
if (s_adbPath.isEmpty() || !fileInfo.isFile()) {
|
||||
s_adbPath = Config::getInstance().getAdbPath();
|
||||
}
|
||||
fileInfo = s_adbPath;
|
||||
if (s_adbPath.isEmpty() || !fileInfo.isFile()) {
|
||||
s_adbPath = QCoreApplication::applicationDirPath() + "/adb";
|
||||
}
|
||||
qInfo("adb path: %s", QDir(s_adbPath).absolutePath().toUtf8().data());
|
||||
}
|
||||
return s_adbPath;
|
||||
}
|
||||
|
||||
void AdbProcess::initSignals()
|
||||
{
|
||||
// aboutToQuit not exit event loop, so deletelater is ok
|
||||
//connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &AdbProcess::deleteLater);
|
||||
|
||||
connect(this, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
if (NormalExit == exitStatus && 0 == exitCode) {
|
||||
emit adbProcessResult(AER_SUCCESS_EXEC);
|
||||
} else {
|
||||
//P7C0218510000537 unauthorized ,手机端此时弹出调试认证,要允许调试
|
||||
emit adbProcessResult(AER_ERROR_EXEC);
|
||||
}
|
||||
qDebug() << "adb return " << exitCode << "exit status " << exitStatus;
|
||||
});
|
||||
|
||||
connect(this, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
|
||||
if (QProcess::FailedToStart == error) {
|
||||
emit adbProcessResult(AER_ERROR_MISSING_BINARY);
|
||||
} else {
|
||||
emit adbProcessResult(AER_ERROR_START);
|
||||
QString err = QString("qprocess start error:%1 %2").arg(program()).arg(arguments().join(" "));
|
||||
qCritical() << err.toStdString().c_str();
|
||||
}
|
||||
});
|
||||
|
||||
connect(this, &QProcess::readyReadStandardError, this, [this]() {
|
||||
QString tmp = QString::fromUtf8(readAllStandardError()).trimmed();
|
||||
m_errorOutput += tmp;
|
||||
qWarning() << QString("AdbProcess::error:%1").arg(tmp).toStdString().data();
|
||||
});
|
||||
|
||||
connect(this, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||
QString tmp = QString::fromUtf8(readAllStandardOutput()).trimmed();
|
||||
m_standardOutput += tmp;
|
||||
qInfo() << QString("AdbProcess::out:%1").arg(tmp).toStdString().data();
|
||||
});
|
||||
|
||||
connect(this, &QProcess::started, this, [this]() { emit adbProcessResult(AER_SUCCESS_START); });
|
||||
}
|
||||
|
||||
void AdbProcess::execute(const QString &serial, const QStringList &args)
|
||||
{
|
||||
m_standardOutput = "";
|
||||
m_errorOutput = "";
|
||||
QStringList adbArgs;
|
||||
if (!serial.isEmpty()) {
|
||||
adbArgs << "-s" << serial;
|
||||
}
|
||||
adbArgs << args;
|
||||
qDebug() << getAdbPath() << adbArgs.join(" ");
|
||||
start(getAdbPath(), adbArgs);
|
||||
}
|
||||
|
||||
bool AdbProcess::isRuning()
|
||||
{
|
||||
if (QProcess::NotRunning == state()) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void AdbProcess::setShowTouchesEnabled(const QString &serial, bool enabled)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "shell"
|
||||
<< "settings"
|
||||
<< "put"
|
||||
<< "system"
|
||||
<< "show_touches";
|
||||
adbArgs << (enabled ? "1" : "0");
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
QStringList AdbProcess::getDevicesSerialFromStdOut()
|
||||
{
|
||||
// get devices serial by adb devices
|
||||
QStringList serials;
|
||||
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), QString::SkipEmptyParts);
|
||||
for (QString deviceInfo : devicesInfoList) {
|
||||
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), QString::SkipEmptyParts);
|
||||
if (2 == deviceInfos.count() && 0 == deviceInfos[1].compare("device")) {
|
||||
serials << deviceInfos[0];
|
||||
}
|
||||
}
|
||||
return serials;
|
||||
}
|
||||
|
||||
QString AdbProcess::getDeviceIPFromStdOut()
|
||||
{
|
||||
QString ip = "";
|
||||
#if 0
|
||||
QString strIPExp = "inet [\\d.]*";
|
||||
QRegExp ipRegExp(strIPExp,Qt::CaseInsensitive);
|
||||
if (ipRegExp.indexIn(m_standardOutput) != -1) {
|
||||
ip = ipRegExp.cap(0);
|
||||
ip = ip.right(ip.size() - 5);
|
||||
}
|
||||
#else
|
||||
QString strIPExp = "inet addr:[\\d.]*";
|
||||
QRegExp ipRegExp(strIPExp, Qt::CaseInsensitive);
|
||||
if (ipRegExp.indexIn(m_standardOutput) != -1) {
|
||||
ip = ipRegExp.cap(0);
|
||||
ip = ip.right(ip.size() - 10);
|
||||
}
|
||||
#endif
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
QString AdbProcess::getDeviceIPByIpFromStdOut()
|
||||
{
|
||||
QString ip = "";
|
||||
|
||||
QString strIPExp = "wlan0 inet [\\d.]*";
|
||||
QRegExp ipRegExp(strIPExp, Qt::CaseInsensitive);
|
||||
if (ipRegExp.indexIn(m_standardOutput) != -1) {
|
||||
ip = ipRegExp.cap(0);
|
||||
ip = ip.right(ip.size() - 14);
|
||||
}
|
||||
qDebug() << "get ip: " << ip;
|
||||
return ip;
|
||||
}
|
||||
|
||||
QString AdbProcess::getStdOut()
|
||||
{
|
||||
return m_standardOutput;
|
||||
}
|
||||
|
||||
QString AdbProcess::getErrorOut()
|
||||
{
|
||||
return m_errorOutput;
|
||||
}
|
||||
|
||||
void AdbProcess::forward(const QString &serial, quint16 localPort, const QString &deviceSocketName)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "forward";
|
||||
adbArgs << QString("tcp:%1").arg(localPort);
|
||||
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::forwardRemove(const QString &serial, quint16 localPort)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "forward";
|
||||
adbArgs << "--remove";
|
||||
adbArgs << QString("tcp:%1").arg(localPort);
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::reverse(const QString &serial, const QString &deviceSocketName, quint16 localPort)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "reverse";
|
||||
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
|
||||
adbArgs << QString("tcp:%1").arg(localPort);
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::reverseRemove(const QString &serial, const QString &deviceSocketName)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "reverse";
|
||||
adbArgs << "--remove";
|
||||
adbArgs << QString("localabstract:%1").arg(deviceSocketName);
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::push(const QString &serial, const QString &local, const QString &remote)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "push";
|
||||
adbArgs << local;
|
||||
adbArgs << remote;
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::install(const QString &serial, const QString &local)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "install";
|
||||
adbArgs << "-r";
|
||||
adbArgs << local;
|
||||
execute(serial, adbArgs);
|
||||
}
|
||||
|
||||
void AdbProcess::removePath(const QString &serial, const QString &path)
|
||||
{
|
||||
QStringList adbArgs;
|
||||
adbArgs << "shell";
|
||||
adbArgs << "rm";
|
||||
adbArgs << path;
|
||||
execute(serial, adbArgs);
|
||||
}
|
53
QtScrcpy/adb/adbprocess.h
Normal file
53
QtScrcpy/adb/adbprocess.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
#ifndef ADBPROCESS_H
|
||||
#define ADBPROCESS_H
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
class AdbProcess : public QProcess
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum ADB_EXEC_RESULT
|
||||
{
|
||||
AER_SUCCESS_START, // 启动成功
|
||||
AER_ERROR_START, // 启动失败
|
||||
AER_SUCCESS_EXEC, // 执行成功
|
||||
AER_ERROR_EXEC, // 执行失败
|
||||
AER_ERROR_MISSING_BINARY, // 找不到文件
|
||||
};
|
||||
|
||||
explicit AdbProcess(QObject *parent = nullptr);
|
||||
virtual ~AdbProcess();
|
||||
|
||||
void execute(const QString &serial, const QStringList &args);
|
||||
void forward(const QString &serial, quint16 localPort, const QString &deviceSocketName);
|
||||
void forwardRemove(const QString &serial, quint16 localPort);
|
||||
void reverse(const QString &serial, const QString &deviceSocketName, quint16 localPort);
|
||||
void reverseRemove(const QString &serial, const QString &deviceSocketName);
|
||||
void push(const QString &serial, const QString &local, const QString &remote);
|
||||
void install(const QString &serial, const QString &local);
|
||||
void removePath(const QString &serial, const QString &path);
|
||||
bool isRuning();
|
||||
void setShowTouchesEnabled(const QString &serial, bool enabled);
|
||||
QStringList getDevicesSerialFromStdOut();
|
||||
QString getDeviceIPFromStdOut();
|
||||
QString getDeviceIPByIpFromStdOut();
|
||||
QString getStdOut();
|
||||
QString getErrorOut();
|
||||
|
||||
static const QString &getAdbPath();
|
||||
|
||||
signals:
|
||||
void adbProcessResult(ADB_EXEC_RESULT processResult);
|
||||
|
||||
private:
|
||||
void initSignals();
|
||||
|
||||
private:
|
||||
QString m_standardOutput = "";
|
||||
QString m_errorOutput = "";
|
||||
static QString s_adbPath;
|
||||
};
|
||||
|
||||
#endif // ADBPROCESS_H
|
|
@ -1,235 +0,0 @@
|
|||
#include <QTcpSocket>
|
||||
#include <QHostAddress>
|
||||
#include <QAudioOutput>
|
||||
#include <QTime>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#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.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();
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#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
|
2
QtScrcpy/common/common.pri
Normal file
2
QtScrcpy/common/common.pri
Normal file
|
@ -0,0 +1,2 @@
|
|||
HEADERS += \
|
||||
$$PWD/qscrcpyevent.h
|
22
QtScrcpy/common/qscrcpyevent.h
Normal file
22
QtScrcpy/common/qscrcpyevent.h
Normal file
|
@ -0,0 +1,22 @@
|
|||
#ifndef QSCRCPYEVENT_H
|
||||
#define QSCRCPYEVENT_H
|
||||
#include <QEvent>
|
||||
|
||||
class QScrcpyEvent : public QEvent
|
||||
{
|
||||
public:
|
||||
enum Type
|
||||
{
|
||||
VideoSocket = QEvent::User + 1,
|
||||
Control,
|
||||
};
|
||||
QScrcpyEvent(Type type) : QEvent(QEvent::Type(type)) {}
|
||||
};
|
||||
|
||||
// VideoSocketEvent
|
||||
class VideoSocketEvent : public QScrcpyEvent
|
||||
{
|
||||
public:
|
||||
VideoSocketEvent() : QScrcpyEvent(VideoSocket) {}
|
||||
};
|
||||
#endif // QSCRCPYEVENT_H
|
3
QtScrcpy/device/android/android.pri
Normal file
3
QtScrcpy/device/android/android.pri
Normal file
|
@ -0,0 +1,3 @@
|
|||
HEADERS += \
|
||||
$$PWD/input.h \
|
||||
$$PWD/keycodes.h
|
840
QtScrcpy/device/android/input.h
Normal file
840
QtScrcpy/device/android/input.h
Normal file
|
@ -0,0 +1,840 @@
|
|||
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/input.h>
|
||||
// blob 08299899b6305a0fe74d7d2b8471b7cd0af49dc7
|
||||
// (and modified)
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _ANDROID_INPUT_H
|
||||
#define _ANDROID_INPUT_H
|
||||
|
||||
/**
|
||||
* Meta key / modifer state.
|
||||
*/
|
||||
enum AndroidMetastate
|
||||
{
|
||||
/** No meta keys are pressed. */
|
||||
AMETA_NONE = 0,
|
||||
|
||||
/** This mask is used to check whether one of the ALT meta keys is pressed. */
|
||||
AMETA_ALT_ON = 0x02,
|
||||
|
||||
/** This mask is used to check whether the left ALT meta key is pressed. */
|
||||
AMETA_ALT_LEFT_ON = 0x10,
|
||||
|
||||
/** This mask is used to check whether the right ALT meta key is pressed. */
|
||||
AMETA_ALT_RIGHT_ON = 0x20,
|
||||
|
||||
/** This mask is used to check whether one of the SHIFT meta keys is pressed. */
|
||||
AMETA_SHIFT_ON = 0x01,
|
||||
|
||||
/** This mask is used to check whether the left SHIFT meta key is pressed. */
|
||||
AMETA_SHIFT_LEFT_ON = 0x40,
|
||||
|
||||
/** This mask is used to check whether the right SHIFT meta key is pressed. */
|
||||
AMETA_SHIFT_RIGHT_ON = 0x80,
|
||||
|
||||
/** This mask is used to check whether the SYM meta key is pressed. */
|
||||
AMETA_SYM_ON = 0x04,
|
||||
|
||||
/** This mask is used to check whether the FUNCTION meta key is pressed. */
|
||||
AMETA_FUNCTION_ON = 0x08,
|
||||
|
||||
/** This mask is used to check whether one of the CTRL meta keys is pressed. */
|
||||
AMETA_CTRL_ON = 0x1000,
|
||||
|
||||
/** This mask is used to check whether the left CTRL meta key is pressed. */
|
||||
AMETA_CTRL_LEFT_ON = 0x2000,
|
||||
|
||||
/** This mask is used to check whether the right CTRL meta key is pressed. */
|
||||
AMETA_CTRL_RIGHT_ON = 0x4000,
|
||||
|
||||
/** This mask is used to check whether one of the META meta keys is pressed. */
|
||||
AMETA_META_ON = 0x10000,
|
||||
|
||||
/** This mask is used to check whether the left META meta key is pressed. */
|
||||
AMETA_META_LEFT_ON = 0x20000,
|
||||
|
||||
/** This mask is used to check whether the right META meta key is pressed. */
|
||||
AMETA_META_RIGHT_ON = 0x40000,
|
||||
|
||||
/** This mask is used to check whether the CAPS LOCK meta key is on. */
|
||||
AMETA_CAPS_LOCK_ON = 0x100000,
|
||||
|
||||
/** This mask is used to check whether the NUM LOCK meta key is on. */
|
||||
AMETA_NUM_LOCK_ON = 0x200000,
|
||||
|
||||
/** This mask is used to check whether the SCROLL LOCK meta key is on. */
|
||||
AMETA_SCROLL_LOCK_ON = 0x400000,
|
||||
};
|
||||
|
||||
/**
|
||||
* Input event types.
|
||||
*/
|
||||
enum AndroidInputEventType
|
||||
{
|
||||
/** Indicates that the input event is a key event. */
|
||||
AINPUT_EVENT_TYPE_KEY = 1,
|
||||
/** Indicates that the input event is a motion event. */
|
||||
AINPUT_EVENT_TYPE_MOTION = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Key event actions.
|
||||
*/
|
||||
enum AndroidKeyeventAction
|
||||
{
|
||||
/** The key has been pressed down. */
|
||||
AKEY_EVENT_ACTION_DOWN = 0,
|
||||
|
||||
/** The key has been released. */
|
||||
AKEY_EVENT_ACTION_UP = 1,
|
||||
|
||||
/**
|
||||
* Multiple duplicate key events have occurred in a row, or a
|
||||
* complex string is being delivered. The repeat_count property
|
||||
* of the key event contains the number of times the given key
|
||||
* code should be executed.
|
||||
*/
|
||||
AKEY_EVENT_ACTION_MULTIPLE = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Key event flags.
|
||||
*/
|
||||
enum AndroidKeyeventFlags
|
||||
{
|
||||
/** This mask is set if the device woke because of this key event. */
|
||||
AKEY_EVENT_FLAG_WOKE_HERE = 0x1,
|
||||
|
||||
/** This mask is set if the key event was generated by a software keyboard. */
|
||||
AKEY_EVENT_FLAG_SOFT_KEYBOARD = 0x2,
|
||||
|
||||
/** This mask is set if we don't want the key event to cause us to leave touch mode. */
|
||||
AKEY_EVENT_FLAG_KEEP_TOUCH_MODE = 0x4,
|
||||
|
||||
/**
|
||||
* This mask is set if an event was known to come from a trusted
|
||||
* part of the system. That is, the event is known to come from
|
||||
* the user, and could not have been spoofed by a third party
|
||||
* component.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_FROM_SYSTEM = 0x8,
|
||||
|
||||
/**
|
||||
* This mask is used for compatibility, to identify enter keys that are
|
||||
* coming from an IME whose enter key has been auto-labelled "next" or
|
||||
* "done". This allows TextView to dispatch these as normal enter keys
|
||||
* for old applications, but still do the appropriate action when
|
||||
* receiving them.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_EDITOR_ACTION = 0x10,
|
||||
|
||||
/**
|
||||
* When associated with up key events, this indicates that the key press
|
||||
* has been canceled. Typically this is used with virtual touch screen
|
||||
* keys, where the user can slide from the virtual key area on to the
|
||||
* display: in that case, the application will receive a canceled up
|
||||
* event and should not perform the action normally associated with the
|
||||
* key. Note that for this to work, the application can not perform an
|
||||
* action for a key until it receives an up or the long press timeout has
|
||||
* expired.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_CANCELED = 0x20,
|
||||
|
||||
/**
|
||||
* This key event was generated by a virtual (on-screen) hard key area.
|
||||
* Typically this is an area of the touchscreen, outside of the regular
|
||||
* display, dedicated to "hardware" buttons.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_VIRTUAL_HARD_KEY = 0x40,
|
||||
|
||||
/**
|
||||
* This flag is set for the first key repeat that occurs after the
|
||||
* long press timeout.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
|
||||
|
||||
/**
|
||||
* Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
|
||||
* press action was executed while it was down.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
|
||||
|
||||
/**
|
||||
* Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
|
||||
* tracked from its initial down. That is, somebody requested that tracking
|
||||
* started on the key down and a long press has not caused
|
||||
* the tracking to be canceled.
|
||||
*/
|
||||
AKEY_EVENT_FLAG_TRACKING = 0x200,
|
||||
|
||||
/**
|
||||
* Set when a key event has been synthesized to implement default behavior
|
||||
* for an event that the application did not handle.
|
||||
* Fallback key events are generated by unhandled trackball motions
|
||||
* (to emulate a directional keypad) and by certain unhandled key presses
|
||||
* that are declared in the key map (such as special function numeric keypad
|
||||
* keys when numlock is off).
|
||||
*/
|
||||
AKEY_EVENT_FLAG_FALLBACK = 0x400,
|
||||
};
|
||||
|
||||
/**
|
||||
* Bit shift for the action bits holding the pointer index as
|
||||
* defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
|
||||
*/
|
||||
#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
|
||||
|
||||
/** Motion event actions */
|
||||
enum AndroidMotioneventAction
|
||||
{
|
||||
/** Bit mask of the parts of the action code that are the action itself. */
|
||||
AMOTION_EVENT_ACTION_MASK = 0xff,
|
||||
|
||||
/**
|
||||
* Bits in the action code that represent a pointer index, used with
|
||||
* AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
|
||||
* down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
|
||||
* index where the data for the pointer going up or down can be found.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
|
||||
|
||||
/** A pressed gesture has started, the motion contains the initial starting location. */
|
||||
AMOTION_EVENT_ACTION_DOWN = 0,
|
||||
|
||||
/**
|
||||
* A pressed gesture has finished, the motion contains the final release location
|
||||
* as well as any intermediate points since the last down or move event.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_UP = 1,
|
||||
|
||||
/**
|
||||
* A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
|
||||
* AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
|
||||
* any intermediate points since the last down or move event.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_MOVE = 2,
|
||||
|
||||
/**
|
||||
* The current gesture has been aborted.
|
||||
* You will not receive any more points in it. You should treat this as
|
||||
* an up event, but not perform any action that you normally would.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_CANCEL = 3,
|
||||
|
||||
/**
|
||||
* A movement has happened outside of the normal bounds of the UI element.
|
||||
* This does not provide a full gesture, but only the initial location of the movement/touch.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_OUTSIDE = 4,
|
||||
|
||||
/**
|
||||
* A non-primary pointer has gone down.
|
||||
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
|
||||
|
||||
/**
|
||||
* A non-primary pointer has gone up.
|
||||
* The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_POINTER_UP = 6,
|
||||
|
||||
/**
|
||||
* A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
|
||||
* The motion contains the most recent point, as well as any intermediate points since
|
||||
* the last hover move event.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_HOVER_MOVE = 7,
|
||||
|
||||
/**
|
||||
* The motion event contains relative vertical and/or horizontal scroll offsets.
|
||||
* Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
|
||||
* and AMOTION_EVENT_AXIS_HSCROLL.
|
||||
* The pointer may or may not be down when this event is dispatched.
|
||||
* This action is always delivered to the winder under the pointer, which
|
||||
* may not be the window currently touched.
|
||||
*/
|
||||
AMOTION_EVENT_ACTION_SCROLL = 8,
|
||||
|
||||
/** The pointer is not down but has entered the boundaries of a window or view. */
|
||||
AMOTION_EVENT_ACTION_HOVER_ENTER = 9,
|
||||
|
||||
/** The pointer is not down but has exited the boundaries of a window or view. */
|
||||
AMOTION_EVENT_ACTION_HOVER_EXIT = 10,
|
||||
|
||||
/* One or more buttons have been pressed. */
|
||||
AMOTION_EVENT_ACTION_BUTTON_PRESS = 11,
|
||||
|
||||
/* One or more buttons have been released. */
|
||||
AMOTION_EVENT_ACTION_BUTTON_RELEASE = 12,
|
||||
};
|
||||
|
||||
/**
|
||||
* Motion event flags.
|
||||
*/
|
||||
enum AndroidMotioneventFlags
|
||||
{
|
||||
/**
|
||||
* This flag indicates that the window that received this motion event is partly
|
||||
* or wholly obscured by another visible window above it. This flag is set to true
|
||||
* even if the event did not directly pass through the obscured area.
|
||||
* A security sensitive application can check this flag to identify situations in which
|
||||
* a malicious application may have covered up part of its content for the purpose
|
||||
* of misleading the user or hijacking touches. An appropriate response might be
|
||||
* to drop the suspect touches or to take additional precautions to confirm the user's
|
||||
* actual intent.
|
||||
*/
|
||||
AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED = 0x1,
|
||||
};
|
||||
|
||||
/**
|
||||
* Motion event edge touch flags.
|
||||
*/
|
||||
enum AndroidMotioneventEdgeTouchTlags
|
||||
{
|
||||
/** No edges intersected. */
|
||||
AMOTION_EVENT_EDGE_FLAG_NONE = 0,
|
||||
|
||||
/** Flag indicating the motion event intersected the top edge of the screen. */
|
||||
AMOTION_EVENT_EDGE_FLAG_TOP = 0x01,
|
||||
|
||||
/** Flag indicating the motion event intersected the bottom edge of the screen. */
|
||||
AMOTION_EVENT_EDGE_FLAG_BOTTOM = 0x02,
|
||||
|
||||
/** Flag indicating the motion event intersected the left edge of the screen. */
|
||||
AMOTION_EVENT_EDGE_FLAG_LEFT = 0x04,
|
||||
|
||||
/** Flag indicating the motion event intersected the right edge of the screen. */
|
||||
AMOTION_EVENT_EDGE_FLAG_RIGHT = 0x08
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants that identify each individual axis of a motion event.
|
||||
* @anchor AMOTION_EVENT_AXIS
|
||||
*/
|
||||
enum AndroidMotioneventAxis
|
||||
{
|
||||
/**
|
||||
* Axis constant: X axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the absolute X screen position of the center of
|
||||
* the touch contact area. The units are display pixels.
|
||||
* - For a touch pad, reports the absolute X surface position of the center of the touch
|
||||
* contact area. The units are device-dependent.
|
||||
* - For a mouse, reports the absolute X screen position of the mouse pointer.
|
||||
* The units are display pixels.
|
||||
* - For a trackball, reports the relative horizontal displacement of the trackball.
|
||||
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
|
||||
* - For a joystick, reports the absolute X position of the joystick.
|
||||
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_X = 0,
|
||||
/**
|
||||
* Axis constant: Y axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the absolute Y screen position of the center of
|
||||
* the touch contact area. The units are display pixels.
|
||||
* - For a touch pad, reports the absolute Y surface position of the center of the touch
|
||||
* contact area. The units are device-dependent.
|
||||
* - For a mouse, reports the absolute Y screen position of the mouse pointer.
|
||||
* The units are display pixels.
|
||||
* - For a trackball, reports the relative vertical displacement of the trackball.
|
||||
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
|
||||
* - For a joystick, reports the absolute Y position of the joystick.
|
||||
* The value is normalized to a range from -1.0 (up or far) to 1.0 (down or near).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_Y = 1,
|
||||
/**
|
||||
* Axis constant: Pressure axis of a motion event.
|
||||
*
|
||||
* - For a touch screen or touch pad, reports the approximate pressure applied to the surface
|
||||
* by a finger or other tool. The value is normalized to a range from
|
||||
* 0 (no pressure at all) to 1 (normal pressure), although values higher than 1
|
||||
* may be generated depending on the calibration of the input device.
|
||||
* - For a trackball, the value is set to 1 if the trackball button is pressed
|
||||
* or 0 otherwise.
|
||||
* - For a mouse, the value is set to 1 if the primary mouse button is pressed
|
||||
* or 0 otherwise.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_PRESSURE = 2,
|
||||
/**
|
||||
* Axis constant: Size axis of a motion event.
|
||||
*
|
||||
* - For a touch screen or touch pad, reports the approximate size of the contact area in
|
||||
* relation to the maximum detectable size for the device. The value is normalized
|
||||
* to a range from 0 (smallest detectable size) to 1 (largest detectable size),
|
||||
* although it is not a linear scale. This value is of limited use.
|
||||
* To obtain calibrated size information, see
|
||||
* {@link AMOTION_EVENT_AXIS_TOUCH_MAJOR} or {@link AMOTION_EVENT_AXIS_TOOL_MAJOR}.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_SIZE = 3,
|
||||
/**
|
||||
* Axis constant: TouchMajor axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the length of the major axis of an ellipse that
|
||||
* represents the touch area at the point of contact.
|
||||
* The units are display pixels.
|
||||
* - For a touch pad, reports the length of the major axis of an ellipse that
|
||||
* represents the touch area at the point of contact.
|
||||
* The units are device-dependent.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_TOUCH_MAJOR = 4,
|
||||
/**
|
||||
* Axis constant: TouchMinor axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the length of the minor axis of an ellipse that
|
||||
* represents the touch area at the point of contact.
|
||||
* The units are display pixels.
|
||||
* - For a touch pad, reports the length of the minor axis of an ellipse that
|
||||
* represents the touch area at the point of contact.
|
||||
* The units are device-dependent.
|
||||
*
|
||||
* When the touch is circular, the major and minor axis lengths will be equal to one another.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_TOUCH_MINOR = 5,
|
||||
/**
|
||||
* Axis constant: ToolMajor axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the length of the major axis of an ellipse that
|
||||
* represents the size of the approaching finger or tool used to make contact.
|
||||
* - For a touch pad, reports the length of the major axis of an ellipse that
|
||||
* represents the size of the approaching finger or tool used to make contact.
|
||||
* The units are device-dependent.
|
||||
*
|
||||
* When the touch is circular, the major and minor axis lengths will be equal to one another.
|
||||
*
|
||||
* The tool size may be larger than the touch size since the tool may not be fully
|
||||
* in contact with the touch sensor.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_TOOL_MAJOR = 6,
|
||||
/**
|
||||
* Axis constant: ToolMinor axis of a motion event.
|
||||
*
|
||||
* - For a touch screen, reports the length of the minor axis of an ellipse that
|
||||
* represents the size of the approaching finger or tool used to make contact.
|
||||
* - For a touch pad, reports the length of the minor axis of an ellipse that
|
||||
* represents the size of the approaching finger or tool used to make contact.
|
||||
* The units are device-dependent.
|
||||
*
|
||||
* When the touch is circular, the major and minor axis lengths will be equal to one another.
|
||||
*
|
||||
* The tool size may be larger than the touch size since the tool may not be fully
|
||||
* in contact with the touch sensor.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_TOOL_MINOR = 7,
|
||||
/**
|
||||
* Axis constant: Orientation axis of a motion event.
|
||||
*
|
||||
* - For a touch screen or touch pad, reports the orientation of the finger
|
||||
* or tool in radians relative to the vertical plane of the device.
|
||||
* An angle of 0 radians indicates that the major axis of contact is oriented
|
||||
* upwards, is perfectly circular or is of unknown orientation. A positive angle
|
||||
* indicates that the major axis of contact is oriented to the right. A negative angle
|
||||
* indicates that the major axis of contact is oriented to the left.
|
||||
* The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians
|
||||
* (finger pointing fully right).
|
||||
* - For a stylus, the orientation indicates the direction in which the stylus
|
||||
* is pointing in relation to the vertical axis of the current orientation of the screen.
|
||||
* The range is from -PI radians to PI radians, where 0 is pointing up,
|
||||
* -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
|
||||
* is pointing right. See also {@link AMOTION_EVENT_AXIS_TILT}.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_ORIENTATION = 8,
|
||||
/**
|
||||
* Axis constant: Vertical Scroll axis of a motion event.
|
||||
*
|
||||
* - For a mouse, reports the relative movement of the vertical scroll wheel.
|
||||
* The value is normalized to a range from -1.0 (down) to 1.0 (up).
|
||||
*
|
||||
* This axis should be used to scroll views vertically.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_VSCROLL = 9,
|
||||
/**
|
||||
* Axis constant: Horizontal Scroll axis of a motion event.
|
||||
*
|
||||
* - For a mouse, reports the relative movement of the horizontal scroll wheel.
|
||||
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
|
||||
*
|
||||
* This axis should be used to scroll views horizontally.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_HSCROLL = 10,
|
||||
/**
|
||||
* Axis constant: Z axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute Z position of the joystick.
|
||||
* The value is normalized to a range from -1.0 (high) to 1.0 (low).
|
||||
* <em>On game pads with two analog joysticks, this axis is often reinterpreted
|
||||
* to report the absolute X position of the second joystick instead.</em>
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_Z = 11,
|
||||
/**
|
||||
* Axis constant: X Rotation axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute rotation angle about the X axis.
|
||||
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RX = 12,
|
||||
/**
|
||||
* Axis constant: Y Rotation axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute rotation angle about the Y axis.
|
||||
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RY = 13,
|
||||
/**
|
||||
* Axis constant: Z Rotation axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute rotation angle about the Z axis.
|
||||
* The value is normalized to a range from -1.0 (counter-clockwise) to 1.0 (clockwise).
|
||||
* On game pads with two analog joysticks, this axis is often reinterpreted
|
||||
* to report the absolute Y position of the second joystick instead.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RZ = 14,
|
||||
/**
|
||||
* Axis constant: Hat X axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute X position of the directional hat control.
|
||||
* The value is normalized to a range from -1.0 (left) to 1.0 (right).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_HAT_X = 15,
|
||||
/**
|
||||
* Axis constant: Hat Y axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute Y position of the directional hat control.
|
||||
* The value is normalized to a range from -1.0 (up) to 1.0 (down).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_HAT_Y = 16,
|
||||
/**
|
||||
* Axis constant: Left Trigger axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the left trigger control.
|
||||
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_LTRIGGER = 17,
|
||||
/**
|
||||
* Axis constant: Right Trigger axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the right trigger control.
|
||||
* The value is normalized to a range from 0.0 (released) to 1.0 (fully pressed).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RTRIGGER = 18,
|
||||
/**
|
||||
* Axis constant: Throttle axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the throttle control.
|
||||
* The value is normalized to a range from 0.0 (fully open) to 1.0 (fully closed).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_THROTTLE = 19,
|
||||
/**
|
||||
* Axis constant: Rudder axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the rudder control.
|
||||
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RUDDER = 20,
|
||||
/**
|
||||
* Axis constant: Wheel axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the steering wheel control.
|
||||
* The value is normalized to a range from -1.0 (turn left) to 1.0 (turn right).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_WHEEL = 21,
|
||||
/**
|
||||
* Axis constant: Gas axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the gas (accelerator) control.
|
||||
* The value is normalized to a range from 0.0 (no acceleration)
|
||||
* to 1.0 (maximum acceleration).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GAS = 22,
|
||||
/**
|
||||
* Axis constant: Brake axis of a motion event.
|
||||
*
|
||||
* - For a joystick, reports the absolute position of the brake control.
|
||||
* The value is normalized to a range from 0.0 (no braking) to 1.0 (maximum braking).
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_BRAKE = 23,
|
||||
/**
|
||||
* Axis constant: Distance axis of a motion event.
|
||||
*
|
||||
* - For a stylus, reports the distance of the stylus from the screen.
|
||||
* A value of 0.0 indicates direct contact and larger values indicate increasing
|
||||
* distance from the surface.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_DISTANCE = 24,
|
||||
/**
|
||||
* Axis constant: Tilt axis of a motion event.
|
||||
*
|
||||
* - For a stylus, reports the tilt angle of the stylus in radians where
|
||||
* 0 radians indicates that the stylus is being held perpendicular to the
|
||||
* surface, and PI/2 radians indicates that the stylus is being held flat
|
||||
* against the surface.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_TILT = 25,
|
||||
/**
|
||||
* Axis constant: Generic scroll axis of a motion event.
|
||||
*
|
||||
* - This is used for scroll axis motion events that can't be classified as strictly
|
||||
* vertical or horizontal. The movement of a rotating scroller is an example of this.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_SCROLL = 26,
|
||||
/**
|
||||
* Axis constant: The movement of x position of a motion event.
|
||||
*
|
||||
* - For a mouse, reports a difference of x position between the previous position.
|
||||
* This is useful when pointer is captured, in that case the mouse pointer doesn't
|
||||
* change the location but this axis reports the difference which allows the app
|
||||
* to see how the mouse is moved.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RELATIVE_X = 27,
|
||||
/**
|
||||
* Axis constant: The movement of y position of a motion event.
|
||||
*
|
||||
* Same as {@link RELATIVE_X}, but for y position.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_RELATIVE_Y = 28,
|
||||
/**
|
||||
* Axis constant: Generic 1 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_1 = 32,
|
||||
/**
|
||||
* Axis constant: Generic 2 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_2 = 33,
|
||||
/**
|
||||
* Axis constant: Generic 3 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_3 = 34,
|
||||
/**
|
||||
* Axis constant: Generic 4 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_4 = 35,
|
||||
/**
|
||||
* Axis constant: Generic 5 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_5 = 36,
|
||||
/**
|
||||
* Axis constant: Generic 6 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_6 = 37,
|
||||
/**
|
||||
* Axis constant: Generic 7 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_7 = 38,
|
||||
/**
|
||||
* Axis constant: Generic 8 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_8 = 39,
|
||||
/**
|
||||
* Axis constant: Generic 9 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_9 = 40,
|
||||
/**
|
||||
* Axis constant: Generic 10 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_10 = 41,
|
||||
/**
|
||||
* Axis constant: Generic 11 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_11 = 42,
|
||||
/**
|
||||
* Axis constant: Generic 12 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_12 = 43,
|
||||
/**
|
||||
* Axis constant: Generic 13 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_13 = 44,
|
||||
/**
|
||||
* Axis constant: Generic 14 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_14 = 45,
|
||||
/**
|
||||
* Axis constant: Generic 15 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_15 = 46,
|
||||
/**
|
||||
* Axis constant: Generic 16 axis of a motion event.
|
||||
* The interpretation of a generic axis is device-specific.
|
||||
*/
|
||||
AMOTION_EVENT_AXIS_GENERIC_16 = 47,
|
||||
|
||||
// NOTE: If you add a new axis here you must also add it to several other files.
|
||||
// Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants that identify buttons that are associated with motion events.
|
||||
* Refer to the documentation on the MotionEvent class for descriptions of each button.
|
||||
*/
|
||||
enum AndroidMotioneventButtons
|
||||
{
|
||||
/** primary */
|
||||
AMOTION_EVENT_BUTTON_PRIMARY = 1 << 0,
|
||||
/** secondary */
|
||||
AMOTION_EVENT_BUTTON_SECONDARY = 1 << 1,
|
||||
/** tertiary */
|
||||
AMOTION_EVENT_BUTTON_TERTIARY = 1 << 2,
|
||||
/** back */
|
||||
AMOTION_EVENT_BUTTON_BACK = 1 << 3,
|
||||
/** forward */
|
||||
AMOTION_EVENT_BUTTON_FORWARD = 1 << 4,
|
||||
AMOTION_EVENT_BUTTON_STYLUS_PRIMARY = 1 << 5,
|
||||
AMOTION_EVENT_BUTTON_STYLUS_SECONDARY = 1 << 6,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants that identify tool types.
|
||||
* Refer to the documentation on the MotionEvent class for descriptions of each tool type.
|
||||
*/
|
||||
enum AndroidMotioneventToolType
|
||||
{
|
||||
/** unknown */
|
||||
AMOTION_EVENT_TOOL_TYPE_UNKNOWN = 0,
|
||||
/** finger */
|
||||
AMOTION_EVENT_TOOL_TYPE_FINGER = 1,
|
||||
/** stylus */
|
||||
AMOTION_EVENT_TOOL_TYPE_STYLUS = 2,
|
||||
/** mouse */
|
||||
AMOTION_EVENT_TOOL_TYPE_MOUSE = 3,
|
||||
/** eraser */
|
||||
AMOTION_EVENT_TOOL_TYPE_ERASER = 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Input source masks.
|
||||
*
|
||||
* Refer to the documentation on android.view.InputDevice for more details about input sources
|
||||
* and their correct interpretation.
|
||||
*/
|
||||
enum AndroidInputSourceClass
|
||||
{
|
||||
/** mask */
|
||||
AINPUT_SOURCE_CLASS_MASK = 0x000000ff,
|
||||
|
||||
/** none */
|
||||
AINPUT_SOURCE_CLASS_NONE = 0x00000000,
|
||||
/** button */
|
||||
AINPUT_SOURCE_CLASS_BUTTON = 0x00000001,
|
||||
/** pointer */
|
||||
AINPUT_SOURCE_CLASS_POINTER = 0x00000002,
|
||||
/** navigation */
|
||||
AINPUT_SOURCE_CLASS_NAVIGATION = 0x00000004,
|
||||
/** position */
|
||||
AINPUT_SOURCE_CLASS_POSITION = 0x00000008,
|
||||
/** joystick */
|
||||
AINPUT_SOURCE_CLASS_JOYSTICK = 0x00000010,
|
||||
};
|
||||
|
||||
/**
|
||||
* Input sources.
|
||||
*/
|
||||
enum AndroidInputSource
|
||||
{
|
||||
/** unknown */
|
||||
AINPUT_SOURCE_UNKNOWN = 0x00000000,
|
||||
|
||||
/** keyboard */
|
||||
AINPUT_SOURCE_KEYBOARD = 0x00000100 | AINPUT_SOURCE_CLASS_BUTTON,
|
||||
/** dpad */
|
||||
AINPUT_SOURCE_DPAD = 0x00000200 | AINPUT_SOURCE_CLASS_BUTTON,
|
||||
/** gamepad */
|
||||
AINPUT_SOURCE_GAMEPAD = 0x00000400 | AINPUT_SOURCE_CLASS_BUTTON,
|
||||
/** touchscreen */
|
||||
AINPUT_SOURCE_TOUCHSCREEN = 0x00001000 | AINPUT_SOURCE_CLASS_POINTER,
|
||||
/** mouse */
|
||||
AINPUT_SOURCE_MOUSE = 0x00002000 | AINPUT_SOURCE_CLASS_POINTER,
|
||||
/** stylus */
|
||||
AINPUT_SOURCE_STYLUS = 0x00004000 | AINPUT_SOURCE_CLASS_POINTER,
|
||||
/** bluetooth stylus */
|
||||
AINPUT_SOURCE_BLUETOOTH_STYLUS = 0x00008000 | AINPUT_SOURCE_STYLUS,
|
||||
/** trackball */
|
||||
AINPUT_SOURCE_TRACKBALL = 0x00010000 | AINPUT_SOURCE_CLASS_NAVIGATION,
|
||||
/** mouse relative */
|
||||
AINPUT_SOURCE_MOUSE_RELATIVE = 0x00020000 | AINPUT_SOURCE_CLASS_NAVIGATION,
|
||||
/** touchpad */
|
||||
AINPUT_SOURCE_TOUCHPAD = 0x00100000 | AINPUT_SOURCE_CLASS_POSITION,
|
||||
/** navigation */
|
||||
AINPUT_SOURCE_TOUCH_NAVIGATION = 0x00200000 | AINPUT_SOURCE_CLASS_NONE,
|
||||
/** joystick */
|
||||
AINPUT_SOURCE_JOYSTICK = 0x01000000 | AINPUT_SOURCE_CLASS_JOYSTICK,
|
||||
/** rotary encoder */
|
||||
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
|
||||
};
|
||||
|
||||
/**
|
||||
* Keyboard types.
|
||||
*
|
||||
* Refer to the documentation on android.view.InputDevice for more details.
|
||||
*/
|
||||
enum AndroidKeyboardType
|
||||
{
|
||||
/** none */
|
||||
AINPUT_KEYBOARD_TYPE_NONE = 0,
|
||||
/** non alphabetic */
|
||||
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC = 1,
|
||||
/** alphabetic */
|
||||
AINPUT_KEYBOARD_TYPE_ALPHABETIC = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constants used to retrieve information about the range of motion for a particular
|
||||
* coordinate of a motion event.
|
||||
*
|
||||
* Refer to the documentation on android.view.InputDevice for more details about input sources
|
||||
* and their correct interpretation.
|
||||
*
|
||||
* @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*} constants instead.
|
||||
*/
|
||||
enum AndroidMotionRange
|
||||
{
|
||||
/** x */
|
||||
AINPUT_MOTION_RANGE_X = AMOTION_EVENT_AXIS_X,
|
||||
/** y */
|
||||
AINPUT_MOTION_RANGE_Y = AMOTION_EVENT_AXIS_Y,
|
||||
/** pressure */
|
||||
AINPUT_MOTION_RANGE_PRESSURE = AMOTION_EVENT_AXIS_PRESSURE,
|
||||
/** size */
|
||||
AINPUT_MOTION_RANGE_SIZE = AMOTION_EVENT_AXIS_SIZE,
|
||||
/** touch major */
|
||||
AINPUT_MOTION_RANGE_TOUCH_MAJOR = AMOTION_EVENT_AXIS_TOUCH_MAJOR,
|
||||
/** touch minor */
|
||||
AINPUT_MOTION_RANGE_TOUCH_MINOR = AMOTION_EVENT_AXIS_TOUCH_MINOR,
|
||||
/** tool major */
|
||||
AINPUT_MOTION_RANGE_TOOL_MAJOR = AMOTION_EVENT_AXIS_TOOL_MAJOR,
|
||||
/** tool minor */
|
||||
AINPUT_MOTION_RANGE_TOOL_MINOR = AMOTION_EVENT_AXIS_TOOL_MINOR,
|
||||
/** orientation */
|
||||
AINPUT_MOTION_RANGE_ORIENTATION = AMOTION_EVENT_AXIS_ORIENTATION,
|
||||
};
|
||||
|
||||
#endif // _ANDROID_INPUT_H
|
746
QtScrcpy/device/android/keycodes.h
Normal file
746
QtScrcpy/device/android/keycodes.h
Normal file
|
@ -0,0 +1,746 @@
|
|||
// copied from <https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h>
|
||||
// blob 2164d6163e1646c22825e364cad4f3c47638effd
|
||||
// (and modified)
|
||||
/*
|
||||
* Copyright (C) 2010 The Android Open Source Project
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _ANDROID_KEYCODES_H
|
||||
#define _ANDROID_KEYCODES_H
|
||||
|
||||
/**
|
||||
* Key codes.
|
||||
*/
|
||||
enum AndroidKeycode
|
||||
{
|
||||
/** Unknown key code. */
|
||||
AKEYCODE_UNKNOWN = 0,
|
||||
/** Soft Left key.
|
||||
* Usually situated below the display on phones and used as a multi-function
|
||||
* feature key for selecting a software defined function shown on the bottom left
|
||||
* of the display. */
|
||||
AKEYCODE_SOFT_LEFT = 1,
|
||||
/** Soft Right key.
|
||||
* Usually situated below the display on phones and used as a multi-function
|
||||
* feature key for selecting a software defined function shown on the bottom right
|
||||
* of the display. */
|
||||
AKEYCODE_SOFT_RIGHT = 2,
|
||||
/** Home key.
|
||||
* This key is handled by the framework and is never delivered to applications. */
|
||||
AKEYCODE_HOME = 3,
|
||||
/** Back key. */
|
||||
AKEYCODE_BACK = 4,
|
||||
/** Call key. */
|
||||
AKEYCODE_CALL = 5,
|
||||
/** End Call key. */
|
||||
AKEYCODE_ENDCALL = 6,
|
||||
/** '0' key. */
|
||||
AKEYCODE_0 = 7,
|
||||
/** '1' key. */
|
||||
AKEYCODE_1 = 8,
|
||||
/** '2' key. */
|
||||
AKEYCODE_2 = 9,
|
||||
/** '3' key. */
|
||||
AKEYCODE_3 = 10,
|
||||
/** '4' key. */
|
||||
AKEYCODE_4 = 11,
|
||||
/** '5' key. */
|
||||
AKEYCODE_5 = 12,
|
||||
/** '6' key. */
|
||||
AKEYCODE_6 = 13,
|
||||
/** '7' key. */
|
||||
AKEYCODE_7 = 14,
|
||||
/** '8' key. */
|
||||
AKEYCODE_8 = 15,
|
||||
/** '9' key. */
|
||||
AKEYCODE_9 = 16,
|
||||
/** '*' key. */
|
||||
AKEYCODE_STAR = 17,
|
||||
/** '#' key. */
|
||||
AKEYCODE_POUND = 18,
|
||||
/** Directional Pad Up key.
|
||||
* May also be synthesized from trackball motions. */
|
||||
AKEYCODE_DPAD_UP = 19,
|
||||
/** Directional Pad Down key.
|
||||
* May also be synthesized from trackball motions. */
|
||||
AKEYCODE_DPAD_DOWN = 20,
|
||||
/** Directional Pad Left key.
|
||||
* May also be synthesized from trackball motions. */
|
||||
AKEYCODE_DPAD_LEFT = 21,
|
||||
/** Directional Pad Right key.
|
||||
* May also be synthesized from trackball motions. */
|
||||
AKEYCODE_DPAD_RIGHT = 22,
|
||||
/** Directional Pad Center key.
|
||||
* May also be synthesized from trackball motions. */
|
||||
AKEYCODE_DPAD_CENTER = 23,
|
||||
/** Volume Up key.
|
||||
* Adjusts the speaker volume up. */
|
||||
AKEYCODE_VOLUME_UP = 24,
|
||||
/** Volume Down key.
|
||||
* Adjusts the speaker volume down. */
|
||||
AKEYCODE_VOLUME_DOWN = 25,
|
||||
/** Power key. */
|
||||
AKEYCODE_POWER = 26,
|
||||
/** Camera key.
|
||||
* Used to launch a camera application or take pictures. */
|
||||
AKEYCODE_CAMERA = 27,
|
||||
/** Clear key. */
|
||||
AKEYCODE_CLEAR = 28,
|
||||
/** 'A' key. */
|
||||
AKEYCODE_A = 29,
|
||||
/** 'B' key. */
|
||||
AKEYCODE_B = 30,
|
||||
/** 'C' key. */
|
||||
AKEYCODE_C = 31,
|
||||
/** 'D' key. */
|
||||
AKEYCODE_D = 32,
|
||||
/** 'E' key. */
|
||||
AKEYCODE_E = 33,
|
||||
/** 'F' key. */
|
||||
AKEYCODE_F = 34,
|
||||
/** 'G' key. */
|
||||
AKEYCODE_G = 35,
|
||||
/** 'H' key. */
|
||||
AKEYCODE_H = 36,
|
||||
/** 'I' key. */
|
||||
AKEYCODE_I = 37,
|
||||
/** 'J' key. */
|
||||
AKEYCODE_J = 38,
|
||||
/** 'K' key. */
|
||||
AKEYCODE_K = 39,
|
||||
/** 'L' key. */
|
||||
AKEYCODE_L = 40,
|
||||
/** 'M' key. */
|
||||
AKEYCODE_M = 41,
|
||||
/** 'N' key. */
|
||||
AKEYCODE_N = 42,
|
||||
/** 'O' key. */
|
||||
AKEYCODE_O = 43,
|
||||
/** 'P' key. */
|
||||
AKEYCODE_P = 44,
|
||||
/** 'Q' key. */
|
||||
AKEYCODE_Q = 45,
|
||||
/** 'R' key. */
|
||||
AKEYCODE_R = 46,
|
||||
/** 'S' key. */
|
||||
AKEYCODE_S = 47,
|
||||
/** 'T' key. */
|
||||
AKEYCODE_T = 48,
|
||||
/** 'U' key. */
|
||||
AKEYCODE_U = 49,
|
||||
/** 'V' key. */
|
||||
AKEYCODE_V = 50,
|
||||
/** 'W' key. */
|
||||
AKEYCODE_W = 51,
|
||||
/** 'X' key. */
|
||||
AKEYCODE_X = 52,
|
||||
/** 'Y' key. */
|
||||
AKEYCODE_Y = 53,
|
||||
/** 'Z' key. */
|
||||
AKEYCODE_Z = 54,
|
||||
/** ',' key. */
|
||||
AKEYCODE_COMMA = 55,
|
||||
/** '.' key. */
|
||||
AKEYCODE_PERIOD = 56,
|
||||
/** Left Alt modifier key. */
|
||||
AKEYCODE_ALT_LEFT = 57,
|
||||
/** Right Alt modifier key. */
|
||||
AKEYCODE_ALT_RIGHT = 58,
|
||||
/** Left Shift modifier key. */
|
||||
AKEYCODE_SHIFT_LEFT = 59,
|
||||
/** Right Shift modifier key. */
|
||||
AKEYCODE_SHIFT_RIGHT = 60,
|
||||
/** Tab key. */
|
||||
AKEYCODE_TAB = 61,
|
||||
/** Space key. */
|
||||
AKEYCODE_SPACE = 62,
|
||||
/** Symbol modifier key.
|
||||
* Used to enter alternate symbols. */
|
||||
AKEYCODE_SYM = 63,
|
||||
/** Explorer special function key.
|
||||
* Used to launch a browser application. */
|
||||
AKEYCODE_EXPLORER = 64,
|
||||
/** Envelope special function key.
|
||||
* Used to launch a mail application. */
|
||||
AKEYCODE_ENVELOPE = 65,
|
||||
/** Enter key. */
|
||||
AKEYCODE_ENTER = 66,
|
||||
/** Backspace key.
|
||||
* Deletes characters before the insertion point, unlike {@link AKEYCODE_FORWARD_DEL}. */
|
||||
AKEYCODE_DEL = 67,
|
||||
/** '`' (backtick) key. */
|
||||
AKEYCODE_GRAVE = 68,
|
||||
/** '-'. */
|
||||
AKEYCODE_MINUS = 69,
|
||||
/** '=' key. */
|
||||
AKEYCODE_EQUALS = 70,
|
||||
/** '[' key. */
|
||||
AKEYCODE_LEFT_BRACKET = 71,
|
||||
/** ']' key. */
|
||||
AKEYCODE_RIGHT_BRACKET = 72,
|
||||
/** '\' key. */
|
||||
AKEYCODE_BACKSLASH = 73,
|
||||
/** ';' key. */
|
||||
AKEYCODE_SEMICOLON = 74,
|
||||
/** ''' (apostrophe) key. */
|
||||
AKEYCODE_APOSTROPHE = 75,
|
||||
/** '/' key. */
|
||||
AKEYCODE_SLASH = 76,
|
||||
/** '@' key. */
|
||||
AKEYCODE_AT = 77,
|
||||
/** Number modifier key.
|
||||
* Used to enter numeric symbols.
|
||||
* This key is not {@link AKEYCODE_NUM_LOCK}; it is more like {@link AKEYCODE_ALT_LEFT}. */
|
||||
AKEYCODE_NUM = 78,
|
||||
/** Headset Hook key.
|
||||
* Used to hang up calls and stop media. */
|
||||
AKEYCODE_HEADSETHOOK = 79,
|
||||
/** Camera Focus key.
|
||||
* Used to focus the camera. */
|
||||
AKEYCODE_FOCUS = 80,
|
||||
/** '+' key. */
|
||||
AKEYCODE_PLUS = 81,
|
||||
/** Menu key. */
|
||||
AKEYCODE_MENU = 82,
|
||||
/** Notification key. */
|
||||
AKEYCODE_NOTIFICATION = 83,
|
||||
/** Search key. */
|
||||
AKEYCODE_SEARCH = 84,
|
||||
/** Play/Pause media key. */
|
||||
AKEYCODE_MEDIA_PLAY_PAUSE = 85,
|
||||
/** Stop media key. */
|
||||
AKEYCODE_MEDIA_STOP = 86,
|
||||
/** Play Next media key. */
|
||||
AKEYCODE_MEDIA_NEXT = 87,
|
||||
/** Play Previous media key. */
|
||||
AKEYCODE_MEDIA_PREVIOUS = 88,
|
||||
/** Rewind media key. */
|
||||
AKEYCODE_MEDIA_REWIND = 89,
|
||||
/** Fast Forward media key. */
|
||||
AKEYCODE_MEDIA_FAST_FORWARD = 90,
|
||||
/** Mute key.
|
||||
* Mutes the microphone, unlike {@link AKEYCODE_VOLUME_MUTE}. */
|
||||
AKEYCODE_MUTE = 91,
|
||||
/** Page Up key. */
|
||||
AKEYCODE_PAGE_UP = 92,
|
||||
/** Page Down key. */
|
||||
AKEYCODE_PAGE_DOWN = 93,
|
||||
/** Picture Symbols modifier key.
|
||||
* Used to switch symbol sets (Emoji, Kao-moji). */
|
||||
AKEYCODE_PICTSYMBOLS = 94,
|
||||
/** Switch Charset modifier key.
|
||||
* Used to switch character sets (Kanji, Katakana). */
|
||||
AKEYCODE_SWITCH_CHARSET = 95,
|
||||
/** A Button key.
|
||||
* On a game controller, the A button should be either the button labeled A
|
||||
* or the first button on the bottom row of controller buttons. */
|
||||
AKEYCODE_BUTTON_A = 96,
|
||||
/** B Button key.
|
||||
* On a game controller, the B button should be either the button labeled B
|
||||
* or the second button on the bottom row of controller buttons. */
|
||||
AKEYCODE_BUTTON_B = 97,
|
||||
/** C Button key.
|
||||
* On a game controller, the C button should be either the button labeled C
|
||||
* or the third button on the bottom row of controller buttons. */
|
||||
AKEYCODE_BUTTON_C = 98,
|
||||
/** X Button key.
|
||||
* On a game controller, the X button should be either the button labeled X
|
||||
* or the first button on the upper row of controller buttons. */
|
||||
AKEYCODE_BUTTON_X = 99,
|
||||
/** Y Button key.
|
||||
* On a game controller, the Y button should be either the button labeled Y
|
||||
* or the second button on the upper row of controller buttons. */
|
||||
AKEYCODE_BUTTON_Y = 100,
|
||||
/** Z Button key.
|
||||
* On a game controller, the Z button should be either the button labeled Z
|
||||
* or the third button on the upper row of controller buttons. */
|
||||
AKEYCODE_BUTTON_Z = 101,
|
||||
/** L1 Button key.
|
||||
* On a game controller, the L1 button should be either the button labeled L1 (or L)
|
||||
* or the top left trigger button. */
|
||||
AKEYCODE_BUTTON_L1 = 102,
|
||||
/** R1 Button key.
|
||||
* On a game controller, the R1 button should be either the button labeled R1 (or R)
|
||||
* or the top right trigger button. */
|
||||
AKEYCODE_BUTTON_R1 = 103,
|
||||
/** L2 Button key.
|
||||
* On a game controller, the L2 button should be either the button labeled L2
|
||||
* or the bottom left trigger button. */
|
||||
AKEYCODE_BUTTON_L2 = 104,
|
||||
/** R2 Button key.
|
||||
* On a game controller, the R2 button should be either the button labeled R2
|
||||
* or the bottom right trigger button. */
|
||||
AKEYCODE_BUTTON_R2 = 105,
|
||||
/** Left Thumb Button key.
|
||||
* On a game controller, the left thumb button indicates that the left (or only)
|
||||
* joystick is pressed. */
|
||||
AKEYCODE_BUTTON_THUMBL = 106,
|
||||
/** Right Thumb Button key.
|
||||
* On a game controller, the right thumb button indicates that the right
|
||||
* joystick is pressed. */
|
||||
AKEYCODE_BUTTON_THUMBR = 107,
|
||||
/** Start Button key.
|
||||
* On a game controller, the button labeled Start. */
|
||||
AKEYCODE_BUTTON_START = 108,
|
||||
/** Select Button key.
|
||||
* On a game controller, the button labeled Select. */
|
||||
AKEYCODE_BUTTON_SELECT = 109,
|
||||
/** Mode Button key.
|
||||
* On a game controller, the button labeled Mode. */
|
||||
AKEYCODE_BUTTON_MODE = 110,
|
||||
/** Escape key. */
|
||||
AKEYCODE_ESCAPE = 111,
|
||||
/** Forward Delete key.
|
||||
* Deletes characters ahead of the insertion point, unlike {@link AKEYCODE_DEL}. */
|
||||
AKEYCODE_FORWARD_DEL = 112,
|
||||
/** Left Control modifier key. */
|
||||
AKEYCODE_CTRL_LEFT = 113,
|
||||
/** Right Control modifier key. */
|
||||
AKEYCODE_CTRL_RIGHT = 114,
|
||||
/** Caps Lock key. */
|
||||
AKEYCODE_CAPS_LOCK = 115,
|
||||
/** Scroll Lock key. */
|
||||
AKEYCODE_SCROLL_LOCK = 116,
|
||||
/** Left Meta modifier key. */
|
||||
AKEYCODE_META_LEFT = 117,
|
||||
/** Right Meta modifier key. */
|
||||
AKEYCODE_META_RIGHT = 118,
|
||||
/** Function modifier key. */
|
||||
AKEYCODE_FUNCTION = 119,
|
||||
/** System Request / Print Screen key. */
|
||||
AKEYCODE_SYSRQ = 120,
|
||||
/** Break / Pause key. */
|
||||
AKEYCODE_BREAK = 121,
|
||||
/** Home Movement key.
|
||||
* Used for scrolling or moving the cursor around to the start of a line
|
||||
* or to the top of a list. */
|
||||
AKEYCODE_MOVE_HOME = 122,
|
||||
/** End Movement key.
|
||||
* Used for scrolling or moving the cursor around to the end of a line
|
||||
* or to the bottom of a list. */
|
||||
AKEYCODE_MOVE_END = 123,
|
||||
/** Insert key.
|
||||
* Toggles insert / overwrite edit mode. */
|
||||
AKEYCODE_INSERT = 124,
|
||||
/** Forward key.
|
||||
* Navigates forward in the history stack. Complement of {@link AKEYCODE_BACK}. */
|
||||
AKEYCODE_FORWARD = 125,
|
||||
/** Play media key. */
|
||||
AKEYCODE_MEDIA_PLAY = 126,
|
||||
/** Pause media key. */
|
||||
AKEYCODE_MEDIA_PAUSE = 127,
|
||||
/** Close media key.
|
||||
* May be used to close a CD tray, for example. */
|
||||
AKEYCODE_MEDIA_CLOSE = 128,
|
||||
/** Eject media key.
|
||||
* May be used to eject a CD tray, for example. */
|
||||
AKEYCODE_MEDIA_EJECT = 129,
|
||||
/** Record media key. */
|
||||
AKEYCODE_MEDIA_RECORD = 130,
|
||||
/** F1 key. */
|
||||
AKEYCODE_F1 = 131,
|
||||
/** F2 key. */
|
||||
AKEYCODE_F2 = 132,
|
||||
/** F3 key. */
|
||||
AKEYCODE_F3 = 133,
|
||||
/** F4 key. */
|
||||
AKEYCODE_F4 = 134,
|
||||
/** F5 key. */
|
||||
AKEYCODE_F5 = 135,
|
||||
/** F6 key. */
|
||||
AKEYCODE_F6 = 136,
|
||||
/** F7 key. */
|
||||
AKEYCODE_F7 = 137,
|
||||
/** F8 key. */
|
||||
AKEYCODE_F8 = 138,
|
||||
/** F9 key. */
|
||||
AKEYCODE_F9 = 139,
|
||||
/** F10 key. */
|
||||
AKEYCODE_F10 = 140,
|
||||
/** F11 key. */
|
||||
AKEYCODE_F11 = 141,
|
||||
/** F12 key. */
|
||||
AKEYCODE_F12 = 142,
|
||||
/** Num Lock key.
|
||||
* This is the Num Lock key; it is different from {@link AKEYCODE_NUM}.
|
||||
* This key alters the behavior of other keys on the numeric keypad. */
|
||||
AKEYCODE_NUM_LOCK = 143,
|
||||
/** Numeric keypad '0' key. */
|
||||
AKEYCODE_NUMPAD_0 = 144,
|
||||
/** Numeric keypad '1' key. */
|
||||
AKEYCODE_NUMPAD_1 = 145,
|
||||
/** Numeric keypad '2' key. */
|
||||
AKEYCODE_NUMPAD_2 = 146,
|
||||
/** Numeric keypad '3' key. */
|
||||
AKEYCODE_NUMPAD_3 = 147,
|
||||
/** Numeric keypad '4' key. */
|
||||
AKEYCODE_NUMPAD_4 = 148,
|
||||
/** Numeric keypad '5' key. */
|
||||
AKEYCODE_NUMPAD_5 = 149,
|
||||
/** Numeric keypad '6' key. */
|
||||
AKEYCODE_NUMPAD_6 = 150,
|
||||
/** Numeric keypad '7' key. */
|
||||
AKEYCODE_NUMPAD_7 = 151,
|
||||
/** Numeric keypad '8' key. */
|
||||
AKEYCODE_NUMPAD_8 = 152,
|
||||
/** Numeric keypad '9' key. */
|
||||
AKEYCODE_NUMPAD_9 = 153,
|
||||
/** Numeric keypad '/' key (for division). */
|
||||
AKEYCODE_NUMPAD_DIVIDE = 154,
|
||||
/** Numeric keypad '*' key (for multiplication). */
|
||||
AKEYCODE_NUMPAD_MULTIPLY = 155,
|
||||
/** Numeric keypad '-' key (for subtraction). */
|
||||
AKEYCODE_NUMPAD_SUBTRACT = 156,
|
||||
/** Numeric keypad '+' key (for addition). */
|
||||
AKEYCODE_NUMPAD_ADD = 157,
|
||||
/** Numeric keypad '.' key (for decimals or digit grouping). */
|
||||
AKEYCODE_NUMPAD_DOT = 158,
|
||||
/** Numeric keypad ',' key (for decimals or digit grouping). */
|
||||
AKEYCODE_NUMPAD_COMMA = 159,
|
||||
/** Numeric keypad Enter key. */
|
||||
AKEYCODE_NUMPAD_ENTER = 160,
|
||||
/** Numeric keypad '=' key. */
|
||||
AKEYCODE_NUMPAD_EQUALS = 161,
|
||||
/** Numeric keypad '(' key. */
|
||||
AKEYCODE_NUMPAD_LEFT_PAREN = 162,
|
||||
/** Numeric keypad ')' key. */
|
||||
AKEYCODE_NUMPAD_RIGHT_PAREN = 163,
|
||||
/** Volume Mute key.
|
||||
* Mutes the speaker, unlike {@link AKEYCODE_MUTE}.
|
||||
* This key should normally be implemented as a toggle such that the first press
|
||||
* mutes the speaker and the second press restores the original volume. */
|
||||
AKEYCODE_VOLUME_MUTE = 164,
|
||||
/** Info key.
|
||||
* Common on TV remotes to show additional information related to what is
|
||||
* currently being viewed. */
|
||||
AKEYCODE_INFO = 165,
|
||||
/** Channel up key.
|
||||
* On TV remotes, increments the television channel. */
|
||||
AKEYCODE_CHANNEL_UP = 166,
|
||||
/** Channel down key.
|
||||
* On TV remotes, decrements the television channel. */
|
||||
AKEYCODE_CHANNEL_DOWN = 167,
|
||||
/** Zoom in key. */
|
||||
AKEYCODE_ZOOM_IN = 168,
|
||||
/** Zoom out key. */
|
||||
AKEYCODE_ZOOM_OUT = 169,
|
||||
/** TV key.
|
||||
* On TV remotes, switches to viewing live TV. */
|
||||
AKEYCODE_TV = 170,
|
||||
/** Window key.
|
||||
* On TV remotes, toggles picture-in-picture mode or other windowing functions. */
|
||||
AKEYCODE_WINDOW = 171,
|
||||
/** Guide key.
|
||||
* On TV remotes, shows a programming guide. */
|
||||
AKEYCODE_GUIDE = 172,
|
||||
/** DVR key.
|
||||
* On some TV remotes, switches to a DVR mode for recorded shows. */
|
||||
AKEYCODE_DVR = 173,
|
||||
/** Bookmark key.
|
||||
* On some TV remotes, bookmarks content or web pages. */
|
||||
AKEYCODE_BOOKMARK = 174,
|
||||
/** Toggle captions key.
|
||||
* Switches the mode for closed-captioning text, for example during television shows. */
|
||||
AKEYCODE_CAPTIONS = 175,
|
||||
/** Settings key.
|
||||
* Starts the system settings activity. */
|
||||
AKEYCODE_SETTINGS = 176,
|
||||
/** TV power key.
|
||||
* On TV remotes, toggles the power on a television screen. */
|
||||
AKEYCODE_TV_POWER = 177,
|
||||
/** TV input key.
|
||||
* On TV remotes, switches the input on a television screen. */
|
||||
AKEYCODE_TV_INPUT = 178,
|
||||
/** Set-top-box power key.
|
||||
* On TV remotes, toggles the power on an external Set-top-box. */
|
||||
AKEYCODE_STB_POWER = 179,
|
||||
/** Set-top-box input key.
|
||||
* On TV remotes, switches the input mode on an external Set-top-box. */
|
||||
AKEYCODE_STB_INPUT = 180,
|
||||
/** A/V Receiver power key.
|
||||
* On TV remotes, toggles the power on an external A/V Receiver. */
|
||||
AKEYCODE_AVR_POWER = 181,
|
||||
/** A/V Receiver input key.
|
||||
* On TV remotes, switches the input mode on an external A/V Receiver. */
|
||||
AKEYCODE_AVR_INPUT = 182,
|
||||
/** Red "programmable" key.
|
||||
* On TV remotes, acts as a contextual/programmable key. */
|
||||
AKEYCODE_PROG_RED = 183,
|
||||
/** Green "programmable" key.
|
||||
* On TV remotes, actsas a contextual/programmable key. */
|
||||
AKEYCODE_PROG_GREEN = 184,
|
||||
/** Yellow "programmable" key.
|
||||
* On TV remotes, acts as a contextual/programmable key. */
|
||||
AKEYCODE_PROG_YELLOW = 185,
|
||||
/** Blue "programmable" key.
|
||||
* On TV remotes, acts as a contextual/programmable key. */
|
||||
AKEYCODE_PROG_BLUE = 186,
|
||||
/** App switch key.
|
||||
* Should bring up the application switcher dialog. */
|
||||
AKEYCODE_APP_SWITCH = 187,
|
||||
/** Generic Game Pad Button #1.*/
|
||||
AKEYCODE_BUTTON_1 = 188,
|
||||
/** Generic Game Pad Button #2.*/
|
||||
AKEYCODE_BUTTON_2 = 189,
|
||||
/** Generic Game Pad Button #3.*/
|
||||
AKEYCODE_BUTTON_3 = 190,
|
||||
/** Generic Game Pad Button #4.*/
|
||||
AKEYCODE_BUTTON_4 = 191,
|
||||
/** Generic Game Pad Button #5.*/
|
||||
AKEYCODE_BUTTON_5 = 192,
|
||||
/** Generic Game Pad Button #6.*/
|
||||
AKEYCODE_BUTTON_6 = 193,
|
||||
/** Generic Game Pad Button #7.*/
|
||||
AKEYCODE_BUTTON_7 = 194,
|
||||
/** Generic Game Pad Button #8.*/
|
||||
AKEYCODE_BUTTON_8 = 195,
|
||||
/** Generic Game Pad Button #9.*/
|
||||
AKEYCODE_BUTTON_9 = 196,
|
||||
/** Generic Game Pad Button #10.*/
|
||||
AKEYCODE_BUTTON_10 = 197,
|
||||
/** Generic Game Pad Button #11.*/
|
||||
AKEYCODE_BUTTON_11 = 198,
|
||||
/** Generic Game Pad Button #12.*/
|
||||
AKEYCODE_BUTTON_12 = 199,
|
||||
/** Generic Game Pad Button #13.*/
|
||||
AKEYCODE_BUTTON_13 = 200,
|
||||
/** Generic Game Pad Button #14.*/
|
||||
AKEYCODE_BUTTON_14 = 201,
|
||||
/** Generic Game Pad Button #15.*/
|
||||
AKEYCODE_BUTTON_15 = 202,
|
||||
/** Generic Game Pad Button #16.*/
|
||||
AKEYCODE_BUTTON_16 = 203,
|
||||
/** Language Switch key.
|
||||
* Toggles the current input language such as switching between English and Japanese on
|
||||
* a QWERTY keyboard. On some devices, the same function may be performed by
|
||||
* pressing Shift+Spacebar. */
|
||||
AKEYCODE_LANGUAGE_SWITCH = 204,
|
||||
/** Manner Mode key.
|
||||
* Toggles silent or vibrate mode on and off to make the device behave more politely
|
||||
* in certain settings such as on a crowded train. On some devices, the key may only
|
||||
* operate when long-pressed. */
|
||||
AKEYCODE_MANNER_MODE = 205,
|
||||
/** 3D Mode key.
|
||||
* Toggles the display between 2D and 3D mode. */
|
||||
AKEYCODE_3D_MODE = 206,
|
||||
/** Contacts special function key.
|
||||
* Used to launch an address book application. */
|
||||
AKEYCODE_CONTACTS = 207,
|
||||
/** Calendar special function key.
|
||||
* Used to launch a calendar application. */
|
||||
AKEYCODE_CALENDAR = 208,
|
||||
/** Music special function key.
|
||||
* Used to launch a music player application. */
|
||||
AKEYCODE_MUSIC = 209,
|
||||
/** Calculator special function key.
|
||||
* Used to launch a calculator application. */
|
||||
AKEYCODE_CALCULATOR = 210,
|
||||
/** Japanese full-width / half-width key. */
|
||||
AKEYCODE_ZENKAKU_HANKAKU = 211,
|
||||
/** Japanese alphanumeric key. */
|
||||
AKEYCODE_EISU = 212,
|
||||
/** Japanese non-conversion key. */
|
||||
AKEYCODE_MUHENKAN = 213,
|
||||
/** Japanese conversion key. */
|
||||
AKEYCODE_HENKAN = 214,
|
||||
/** Japanese katakana / hiragana key. */
|
||||
AKEYCODE_KATAKANA_HIRAGANA = 215,
|
||||
/** Japanese Yen key. */
|
||||
AKEYCODE_YEN = 216,
|
||||
/** Japanese Ro key. */
|
||||
AKEYCODE_RO = 217,
|
||||
/** Japanese kana key. */
|
||||
AKEYCODE_KANA = 218,
|
||||
/** Assist key.
|
||||
* Launches the global assist activity. Not delivered to applications. */
|
||||
AKEYCODE_ASSIST = 219,
|
||||
/** Brightness Down key.
|
||||
* Adjusts the screen brightness down. */
|
||||
AKEYCODE_BRIGHTNESS_DOWN = 220,
|
||||
/** Brightness Up key.
|
||||
* Adjusts the screen brightness up. */
|
||||
AKEYCODE_BRIGHTNESS_UP = 221,
|
||||
/** Audio Track key.
|
||||
* Switches the audio tracks. */
|
||||
AKEYCODE_MEDIA_AUDIO_TRACK = 222,
|
||||
/** Sleep key.
|
||||
* Puts the device to sleep. Behaves somewhat like {@link AKEYCODE_POWER} but it
|
||||
* has no effect if the device is already asleep. */
|
||||
AKEYCODE_SLEEP = 223,
|
||||
/** Wakeup key.
|
||||
* Wakes up the device. Behaves somewhat like {@link AKEYCODE_POWER} but it
|
||||
* has no effect if the device is already awake. */
|
||||
AKEYCODE_WAKEUP = 224,
|
||||
/** Pairing key.
|
||||
* Initiates peripheral pairing mode. Useful for pairing remote control
|
||||
* devices or game controllers, especially if no other input mode is
|
||||
* available. */
|
||||
AKEYCODE_PAIRING = 225,
|
||||
/** Media Top Menu key.
|
||||
* Goes to the top of media menu. */
|
||||
AKEYCODE_MEDIA_TOP_MENU = 226,
|
||||
/** '11' key. */
|
||||
AKEYCODE_11 = 227,
|
||||
/** '12' key. */
|
||||
AKEYCODE_12 = 228,
|
||||
/** Last Channel key.
|
||||
* Goes to the last viewed channel. */
|
||||
AKEYCODE_LAST_CHANNEL = 229,
|
||||
/** TV data service key.
|
||||
* Displays data services like weather, sports. */
|
||||
AKEYCODE_TV_DATA_SERVICE = 230,
|
||||
/** Voice Assist key.
|
||||
* Launches the global voice assist activity. Not delivered to applications. */
|
||||
AKEYCODE_VOICE_ASSIST = 231,
|
||||
/** Radio key.
|
||||
* Toggles TV service / Radio service. */
|
||||
AKEYCODE_TV_RADIO_SERVICE = 232,
|
||||
/** Teletext key.
|
||||
* Displays Teletext service. */
|
||||
AKEYCODE_TV_TELETEXT = 233,
|
||||
/** Number entry key.
|
||||
* Initiates to enter multi-digit channel nubmber when each digit key is assigned
|
||||
* for selecting separate channel. Corresponds to Number Entry Mode (0x1D) of CEC
|
||||
* User Control Code. */
|
||||
AKEYCODE_TV_NUMBER_ENTRY = 234,
|
||||
/** Analog Terrestrial key.
|
||||
* Switches to analog terrestrial broadcast service. */
|
||||
AKEYCODE_TV_TERRESTRIAL_ANALOG = 235,
|
||||
/** Digital Terrestrial key.
|
||||
* Switches to digital terrestrial broadcast service. */
|
||||
AKEYCODE_TV_TERRESTRIAL_DIGITAL = 236,
|
||||
/** Satellite key.
|
||||
* Switches to digital satellite broadcast service. */
|
||||
AKEYCODE_TV_SATELLITE = 237,
|
||||
/** BS key.
|
||||
* Switches to BS digital satellite broadcasting service available in Japan. */
|
||||
AKEYCODE_TV_SATELLITE_BS = 238,
|
||||
/** CS key.
|
||||
* Switches to CS digital satellite broadcasting service available in Japan. */
|
||||
AKEYCODE_TV_SATELLITE_CS = 239,
|
||||
/** BS/CS key.
|
||||
* Toggles between BS and CS digital satellite services. */
|
||||
AKEYCODE_TV_SATELLITE_SERVICE = 240,
|
||||
/** Toggle Network key.
|
||||
* Toggles selecting broacast services. */
|
||||
AKEYCODE_TV_NETWORK = 241,
|
||||
/** Antenna/Cable key.
|
||||
* Toggles broadcast input source between antenna and cable. */
|
||||
AKEYCODE_TV_ANTENNA_CABLE = 242,
|
||||
/** HDMI #1 key.
|
||||
* Switches to HDMI input #1. */
|
||||
AKEYCODE_TV_INPUT_HDMI_1 = 243,
|
||||
/** HDMI #2 key.
|
||||
* Switches to HDMI input #2. */
|
||||
AKEYCODE_TV_INPUT_HDMI_2 = 244,
|
||||
/** HDMI #3 key.
|
||||
* Switches to HDMI input #3. */
|
||||
AKEYCODE_TV_INPUT_HDMI_3 = 245,
|
||||
/** HDMI #4 key.
|
||||
* Switches to HDMI input #4. */
|
||||
AKEYCODE_TV_INPUT_HDMI_4 = 246,
|
||||
/** Composite #1 key.
|
||||
* Switches to composite video input #1. */
|
||||
AKEYCODE_TV_INPUT_COMPOSITE_1 = 247,
|
||||
/** Composite #2 key.
|
||||
* Switches to composite video input #2. */
|
||||
AKEYCODE_TV_INPUT_COMPOSITE_2 = 248,
|
||||
/** Component #1 key.
|
||||
* Switches to component video input #1. */
|
||||
AKEYCODE_TV_INPUT_COMPONENT_1 = 249,
|
||||
/** Component #2 key.
|
||||
* Switches to component video input #2. */
|
||||
AKEYCODE_TV_INPUT_COMPONENT_2 = 250,
|
||||
/** VGA #1 key.
|
||||
* Switches to VGA (analog RGB) input #1. */
|
||||
AKEYCODE_TV_INPUT_VGA_1 = 251,
|
||||
/** Audio description key.
|
||||
* Toggles audio description off / on. */
|
||||
AKEYCODE_TV_AUDIO_DESCRIPTION = 252,
|
||||
/** Audio description mixing volume up key.
|
||||
* Louden audio description volume as compared with normal audio volume. */
|
||||
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP = 253,
|
||||
/** Audio description mixing volume down key.
|
||||
* Lessen audio description volume as compared with normal audio volume. */
|
||||
AKEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN = 254,
|
||||
/** Zoom mode key.
|
||||
* Changes Zoom mode (Normal, Full, Zoom, Wide-zoom, etc.) */
|
||||
AKEYCODE_TV_ZOOM_MODE = 255,
|
||||
/** Contents menu key.
|
||||
* Goes to the title list. Corresponds to Contents Menu (0x0B) of CEC User Control
|
||||
* Code */
|
||||
AKEYCODE_TV_CONTENTS_MENU = 256,
|
||||
/** Media context menu key.
|
||||
* Goes to the context menu of media contents. Corresponds to Media Context-sensitive
|
||||
* Menu (0x11) of CEC User Control Code. */
|
||||
AKEYCODE_TV_MEDIA_CONTEXT_MENU = 257,
|
||||
/** Timer programming key.
|
||||
* Goes to the timer recording menu. Corresponds to Timer Programming (0x54) of
|
||||
* CEC User Control Code. */
|
||||
AKEYCODE_TV_TIMER_PROGRAMMING = 258,
|
||||
/** Help key. */
|
||||
AKEYCODE_HELP = 259,
|
||||
AKEYCODE_NAVIGATE_PREVIOUS = 260,
|
||||
AKEYCODE_NAVIGATE_NEXT = 261,
|
||||
AKEYCODE_NAVIGATE_IN = 262,
|
||||
AKEYCODE_NAVIGATE_OUT = 263,
|
||||
/** Primary stem key for Wear
|
||||
* Main power/reset button on watch. */
|
||||
AKEYCODE_STEM_PRIMARY = 264,
|
||||
/** Generic stem key 1 for Wear */
|
||||
AKEYCODE_STEM_1 = 265,
|
||||
/** Generic stem key 2 for Wear */
|
||||
AKEYCODE_STEM_2 = 266,
|
||||
/** Generic stem key 3 for Wear */
|
||||
AKEYCODE_STEM_3 = 267,
|
||||
/** Directional Pad Up-Left */
|
||||
AKEYCODE_DPAD_UP_LEFT = 268,
|
||||
/** Directional Pad Down-Left */
|
||||
AKEYCODE_DPAD_DOWN_LEFT = 269,
|
||||
/** Directional Pad Up-Right */
|
||||
AKEYCODE_DPAD_UP_RIGHT = 270,
|
||||
/** Directional Pad Down-Right */
|
||||
AKEYCODE_DPAD_DOWN_RIGHT = 271,
|
||||
/** Skip forward media key */
|
||||
AKEYCODE_MEDIA_SKIP_FORWARD = 272,
|
||||
/** Skip backward media key */
|
||||
AKEYCODE_MEDIA_SKIP_BACKWARD = 273,
|
||||
/** Step forward media key.
|
||||
* Steps media forward one from at a time. */
|
||||
AKEYCODE_MEDIA_STEP_FORWARD = 274,
|
||||
/** Step backward media key.
|
||||
* Steps media backward one from at a time. */
|
||||
AKEYCODE_MEDIA_STEP_BACKWARD = 275,
|
||||
/** Put device to sleep unless a wakelock is held. */
|
||||
AKEYCODE_SOFT_SLEEP = 276,
|
||||
/** Cut key. */
|
||||
AKEYCODE_CUT = 277,
|
||||
/** Copy key. */
|
||||
AKEYCODE_COPY = 278,
|
||||
/** Paste key. */
|
||||
AKEYCODE_PASTE = 279,
|
||||
/** fingerprint navigation key, up. */
|
||||
AKEYCODE_SYSTEM_NAVIGATION_UP = 280,
|
||||
/** fingerprint navigation key, down. */
|
||||
AKEYCODE_SYSTEM_NAVIGATION_DOWN = 281,
|
||||
/** fingerprint navigation key, left. */
|
||||
AKEYCODE_SYSTEM_NAVIGATION_LEFT = 282,
|
||||
/** fingerprint navigation key, right. */
|
||||
AKEYCODE_SYSTEM_NAVIGATION_RIGHT = 283,
|
||||
/** all apps */
|
||||
AKEYCODE_ALL_APPS = 284
|
||||
};
|
||||
|
||||
#endif // _ANDROID_KEYCODES_H
|
229
QtScrcpy/device/controller/controller.cpp
Normal file
229
QtScrcpy/device/controller/controller.cpp
Normal file
|
@ -0,0 +1,229 @@
|
|||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
|
||||
#include "controller.h"
|
||||
#include "controlmsg.h"
|
||||
#include "inputconvertgame.h"
|
||||
#include "receiver.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
Controller::Controller(QString gameScript, QObject *parent) : QObject(parent)
|
||||
{
|
||||
m_receiver = new Receiver(this);
|
||||
Q_ASSERT(m_receiver);
|
||||
|
||||
updateScript(gameScript);
|
||||
}
|
||||
|
||||
Controller::~Controller() {}
|
||||
|
||||
void Controller::setControlSocket(QTcpSocket *controlSocket)
|
||||
{
|
||||
if (m_controlSocket || !controlSocket) {
|
||||
return;
|
||||
}
|
||||
m_controlSocket = controlSocket;
|
||||
m_receiver->setControlSocket(controlSocket);
|
||||
}
|
||||
|
||||
void Controller::postControlMsg(ControlMsg *controlMsg)
|
||||
{
|
||||
if (controlMsg) {
|
||||
QCoreApplication::postEvent(this, controlMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::test(QRect rc)
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
|
||||
controlMsg->setInjectTouchMsgData(static_cast<quint64>(POINTER_ID_MOUSE), AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_BUTTON_PRIMARY, rc, 1.0f);
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::updateScript(QString gameScript)
|
||||
{
|
||||
if (m_inputConvert) {
|
||||
delete m_inputConvert;
|
||||
}
|
||||
if (!gameScript.isEmpty()) {
|
||||
InputConvertGame *convertgame = new InputConvertGame(this);
|
||||
convertgame->loadKeyMap(gameScript);
|
||||
m_inputConvert = convertgame;
|
||||
} else {
|
||||
m_inputConvert = new InputConvertNormal(this);
|
||||
}
|
||||
Q_ASSERT(m_inputConvert);
|
||||
connect(m_inputConvert, &InputConvertBase::grabCursor, this, &Controller::grabCursor);
|
||||
}
|
||||
|
||||
void Controller::onPostBackOrScreenOn()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onPostGoHome()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_HOME);
|
||||
}
|
||||
|
||||
void Controller::onPostGoMenu()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_MENU);
|
||||
}
|
||||
|
||||
void Controller::onPostGoBack()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_BACK);
|
||||
}
|
||||
|
||||
void Controller::onPostAppSwitch()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_APP_SWITCH);
|
||||
}
|
||||
|
||||
void Controller::onPostPower()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_POWER);
|
||||
}
|
||||
|
||||
void Controller::onPostVolumeUp()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_VOLUME_UP);
|
||||
}
|
||||
|
||||
void Controller::onPostVolumeDown()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_VOLUME_DOWN);
|
||||
}
|
||||
|
||||
void Controller::onExpandNotificationPanel()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_EXPAND_NOTIFICATION_PANEL);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onCollapseNotificationPanel()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_COLLAPSE_NOTIFICATION_PANEL);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onRequestDeviceClipboard()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onSetDeviceClipboard()
|
||||
{
|
||||
QClipboard *board = QApplication::clipboard();
|
||||
QString text = board->text();
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_SET_CLIPBOARD);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setSetClipboardMsgData(text, true);
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onClipboardPaste()
|
||||
{
|
||||
QClipboard *board = QApplication::clipboard();
|
||||
QString text = board->text();
|
||||
onPostTextInput(text);
|
||||
}
|
||||
|
||||
void Controller::onPostTextInput(QString &text)
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TEXT);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setInjectTextMsgData(text);
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode)
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_SET_SCREEN_POWER_MODE);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setSetScreenPowerModeData(mode);
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (m_inputConvert) {
|
||||
m_inputConvert->mouseEvent(from, frameSize, showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (m_inputConvert) {
|
||||
m_inputConvert->wheelEvent(from, frameSize, showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (m_inputConvert) {
|
||||
m_inputConvert->keyEvent(from, frameSize, showSize);
|
||||
}
|
||||
}
|
||||
|
||||
bool Controller::event(QEvent *event)
|
||||
{
|
||||
if (event && static_cast<ControlMsg::Type>(event->type()) == ControlMsg::Control) {
|
||||
ControlMsg *controlMsg = dynamic_cast<ControlMsg *>(event);
|
||||
if (controlMsg) {
|
||||
sendControl(controlMsg->serializeData());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
bool Controller::sendControl(const QByteArray &buffer)
|
||||
{
|
||||
if (buffer.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
qint32 len = 0;
|
||||
if (m_controlSocket) {
|
||||
len = static_cast<qint32>(m_controlSocket->write(buffer.data(), buffer.length()));
|
||||
}
|
||||
return len == buffer.length() ? true : false;
|
||||
}
|
||||
|
||||
void Controller::postKeyCodeClick(AndroidKeycode keycode)
|
||||
{
|
||||
ControlMsg *controlEventDown = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
|
||||
if (!controlEventDown) {
|
||||
return;
|
||||
}
|
||||
controlEventDown->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_DOWN, keycode, AMETA_NONE);
|
||||
postControlMsg(controlEventDown);
|
||||
|
||||
ControlMsg *controlEventUp = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
|
||||
if (!controlEventUp) {
|
||||
return;
|
||||
}
|
||||
controlEventUp->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_UP, keycode, AMETA_NONE);
|
||||
postControlMsg(controlEventUp);
|
||||
}
|
65
QtScrcpy/device/controller/controller.h
Normal file
65
QtScrcpy/device/controller/controller.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "inputconvertbase.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class Receiver;
|
||||
class InputConvertBase;
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Controller(QString gameScript = "", QObject *parent = Q_NULLPTR);
|
||||
virtual ~Controller();
|
||||
|
||||
void setControlSocket(QTcpSocket *controlSocket);
|
||||
void postControlMsg(ControlMsg *controlMsg);
|
||||
void test(QRect rc);
|
||||
|
||||
void updateScript(QString gameScript = "");
|
||||
|
||||
public slots:
|
||||
void onPostGoBack();
|
||||
void onPostGoHome();
|
||||
void onPostGoMenu();
|
||||
void onPostAppSwitch();
|
||||
void onPostPower();
|
||||
void onPostVolumeUp();
|
||||
void onPostVolumeDown();
|
||||
void onExpandNotificationPanel();
|
||||
void onCollapseNotificationPanel();
|
||||
void onSetScreenPowerMode(ControlMsg::ScreenPowerMode mode);
|
||||
|
||||
// for input convert
|
||||
void onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
|
||||
// turn the screen on if it was off, press BACK otherwise
|
||||
void onPostBackOrScreenOn();
|
||||
void onRequestDeviceClipboard();
|
||||
void onSetDeviceClipboard();
|
||||
void onClipboardPaste();
|
||||
void onPostTextInput(QString &text);
|
||||
|
||||
signals:
|
||||
void grabCursor(bool grab);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event);
|
||||
|
||||
private:
|
||||
bool sendControl(const QByteArray &buffer);
|
||||
void postKeyCodeClick(AndroidKeycode keycode);
|
||||
|
||||
private:
|
||||
QPointer<QTcpSocket> m_controlSocket;
|
||||
QPointer<Receiver> m_receiver;
|
||||
QPointer<InputConvertBase> m_inputConvert;
|
||||
};
|
||||
|
||||
#endif // CONTROLLER_H
|
14
QtScrcpy/device/controller/controller.pri
Normal file
14
QtScrcpy/device/controller/controller.pri
Normal file
|
@ -0,0 +1,14 @@
|
|||
HEADERS += \
|
||||
$$PWD/controller.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/controller.cpp
|
||||
|
||||
include ($$PWD/receiver/receiver.pri)
|
||||
include ($$PWD/inputconvert/inputconvert.pri)
|
||||
|
||||
INCLUDEPATH += \
|
||||
$$PWD/receiver \
|
||||
$$PWD/inputconvert
|
||||
|
||||
|
146
QtScrcpy/device/controller/inputconvert/controlmsg.cpp
Normal file
146
QtScrcpy/device/controller/inputconvert/controlmsg.cpp
Normal file
|
@ -0,0 +1,146 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "bufferutil.h"
|
||||
#include "controlmsg.h"
|
||||
|
||||
ControlMsg::ControlMsg(ControlMsgType controlMsgType) : QScrcpyEvent(Control)
|
||||
{
|
||||
m_data.type = controlMsgType;
|
||||
}
|
||||
|
||||
ControlMsg::~ControlMsg()
|
||||
{
|
||||
if (CMT_SET_CLIPBOARD == m_data.type && Q_NULLPTR != m_data.setClipboard.text) {
|
||||
delete m_data.setClipboard.text;
|
||||
m_data.setClipboard.text = Q_NULLPTR;
|
||||
} else if (CMT_INJECT_TEXT == m_data.type && Q_NULLPTR != m_data.injectText.text) {
|
||||
delete m_data.injectText.text;
|
||||
m_data.injectText.text = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlMsg::setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate)
|
||||
{
|
||||
m_data.injectKeycode.action = action;
|
||||
m_data.injectKeycode.keycode = keycode;
|
||||
m_data.injectKeycode.metastate = metastate;
|
||||
}
|
||||
|
||||
void ControlMsg::setInjectTextMsgData(QString &text)
|
||||
{
|
||||
// write length (2 byte) + string (non nul-terminated)
|
||||
if (CONTROL_MSG_INJECT_TEXT_MAX_LENGTH < text.length()) {
|
||||
// injecting a text takes time, so limit the text length
|
||||
text = text.left(CONTROL_MSG_INJECT_TEXT_MAX_LENGTH);
|
||||
}
|
||||
QByteArray tmp = text.toUtf8();
|
||||
m_data.injectText.text = new char[tmp.length() + 1];
|
||||
memcpy(m_data.injectText.text, tmp.data(), tmp.length());
|
||||
m_data.injectText.text[tmp.length()] = '\0';
|
||||
}
|
||||
|
||||
void ControlMsg::setInjectTouchMsgData(quint64 id, AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position, float pressure)
|
||||
{
|
||||
m_data.injectTouch.id = id;
|
||||
m_data.injectTouch.action = action;
|
||||
m_data.injectTouch.buttons = buttons;
|
||||
m_data.injectTouch.position = position;
|
||||
m_data.injectTouch.pressure = pressure;
|
||||
}
|
||||
|
||||
void ControlMsg::setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll)
|
||||
{
|
||||
m_data.injectScroll.position = position;
|
||||
m_data.injectScroll.hScroll = hScroll;
|
||||
m_data.injectScroll.vScroll = vScroll;
|
||||
}
|
||||
|
||||
void ControlMsg::setSetClipboardMsgData(QString &text, bool paste)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH < text.length()) {
|
||||
text = text.left(CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH);
|
||||
}
|
||||
|
||||
QByteArray tmp = text.toUtf8();
|
||||
m_data.setClipboard.text = new char[tmp.length() + 1];
|
||||
memcpy(m_data.setClipboard.text, tmp.data(), tmp.length());
|
||||
m_data.setClipboard.text[tmp.length()] = '\0';
|
||||
m_data.setClipboard.paste = paste;
|
||||
}
|
||||
|
||||
void ControlMsg::setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode)
|
||||
{
|
||||
m_data.setScreenPowerMode.mode = mode;
|
||||
}
|
||||
|
||||
void ControlMsg::writePosition(QBuffer &buffer, const QRect &value)
|
||||
{
|
||||
BufferUtil::write32(buffer, value.left());
|
||||
BufferUtil::write32(buffer, value.top());
|
||||
BufferUtil::write16(buffer, value.width());
|
||||
BufferUtil::write16(buffer, value.height());
|
||||
}
|
||||
|
||||
quint16 ControlMsg::toFixedPoint16(float f)
|
||||
{
|
||||
Q_ASSERT(f >= 0.0f && f <= 1.0f);
|
||||
quint32 u = f * 0x1p16f; // 2^16
|
||||
if (u >= 0xffff) {
|
||||
u = 0xffff;
|
||||
}
|
||||
return (quint16)u;
|
||||
}
|
||||
|
||||
QByteArray ControlMsg::serializeData()
|
||||
{
|
||||
QByteArray byteArray;
|
||||
QBuffer buffer(&byteArray);
|
||||
buffer.open(QBuffer::WriteOnly);
|
||||
buffer.putChar(m_data.type);
|
||||
|
||||
switch (m_data.type) {
|
||||
case CMT_INJECT_KEYCODE:
|
||||
buffer.putChar(m_data.injectKeycode.action);
|
||||
BufferUtil::write32(buffer, m_data.injectKeycode.keycode);
|
||||
BufferUtil::write32(buffer, m_data.injectKeycode.metastate);
|
||||
break;
|
||||
case CMT_INJECT_TEXT:
|
||||
BufferUtil::write16(buffer, static_cast<quint32>(strlen(m_data.injectText.text)));
|
||||
buffer.write(m_data.injectText.text, strlen(m_data.injectText.text));
|
||||
break;
|
||||
case CMT_INJECT_TOUCH: {
|
||||
buffer.putChar(m_data.injectTouch.action);
|
||||
BufferUtil::write64(buffer, m_data.injectTouch.id);
|
||||
writePosition(buffer, m_data.injectTouch.position);
|
||||
quint16 pressure = toFixedPoint16(m_data.injectTouch.pressure);
|
||||
BufferUtil::write16(buffer, pressure);
|
||||
BufferUtil::write32(buffer, m_data.injectTouch.buttons);
|
||||
} break;
|
||||
case CMT_INJECT_SCROLL:
|
||||
writePosition(buffer, m_data.injectScroll.position);
|
||||
BufferUtil::write32(buffer, m_data.injectScroll.hScroll);
|
||||
BufferUtil::write32(buffer, m_data.injectScroll.vScroll);
|
||||
break;
|
||||
case CMT_SET_CLIPBOARD:
|
||||
buffer.putChar(!!m_data.setClipboard.paste);
|
||||
BufferUtil::write16(buffer, static_cast<quint32>(strlen(m_data.setClipboard.text)));
|
||||
buffer.write(m_data.setClipboard.text, strlen(m_data.setClipboard.text));
|
||||
break;
|
||||
case CMT_SET_SCREEN_POWER_MODE:
|
||||
buffer.putChar(m_data.setScreenPowerMode.mode);
|
||||
break;
|
||||
case CMT_BACK_OR_SCREEN_ON:
|
||||
case CMT_EXPAND_NOTIFICATION_PANEL:
|
||||
case CMT_COLLAPSE_NOTIFICATION_PANEL:
|
||||
case CMT_GET_CLIPBOARD:
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Unknown event type:" << m_data.type;
|
||||
break;
|
||||
}
|
||||
buffer.close();
|
||||
return byteArray;
|
||||
}
|
108
QtScrcpy/device/controller/inputconvert/controlmsg.h
Normal file
108
QtScrcpy/device/controller/inputconvert/controlmsg.h
Normal file
|
@ -0,0 +1,108 @@
|
|||
#ifndef CONTROLMSG_H
|
||||
#define CONTROLMSG_H
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
#include "input.h"
|
||||
#include "keycodes.h"
|
||||
#include "qscrcpyevent.h"
|
||||
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH 4092
|
||||
#define POINTER_ID_MOUSE static_cast<quint64>(-1)
|
||||
// ControlMsg
|
||||
class ControlMsg : public QScrcpyEvent
|
||||
{
|
||||
public:
|
||||
enum ControlMsgType
|
||||
{
|
||||
CMT_NULL = -1,
|
||||
CMT_INJECT_KEYCODE = 0,
|
||||
CMT_INJECT_TEXT,
|
||||
CMT_INJECT_TOUCH,
|
||||
CMT_INJECT_SCROLL,
|
||||
CMT_BACK_OR_SCREEN_ON,
|
||||
CMT_EXPAND_NOTIFICATION_PANEL,
|
||||
CMT_COLLAPSE_NOTIFICATION_PANEL,
|
||||
CMT_GET_CLIPBOARD,
|
||||
CMT_SET_CLIPBOARD,
|
||||
CMT_SET_SCREEN_POWER_MODE
|
||||
};
|
||||
|
||||
enum ScreenPowerMode
|
||||
{
|
||||
// see <https://android.googlesource.com/platform/frameworks/base.git/+/pie-release-2/core/java/android/view/SurfaceControl.java#305>
|
||||
SPM_OFF = 0,
|
||||
SPM_NORMAL = 2,
|
||||
};
|
||||
|
||||
ControlMsg(ControlMsgType controlMsgType);
|
||||
virtual ~ControlMsg();
|
||||
|
||||
void setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, AndroidMetastate metastate);
|
||||
void setInjectTextMsgData(QString &text);
|
||||
// id 代表一个触摸点,最多支持10个触摸点[0,9]
|
||||
// action 只能是AMOTION_EVENT_ACTION_DOWN,AMOTION_EVENT_ACTION_UP,AMOTION_EVENT_ACTION_MOVE
|
||||
// position action动作对应的位置
|
||||
void setInjectTouchMsgData(quint64 id, AndroidMotioneventAction action, AndroidMotioneventButtons buttons, QRect position, float pressure);
|
||||
void setInjectScrollMsgData(QRect position, qint32 hScroll, qint32 vScroll);
|
||||
void setSetClipboardMsgData(QString &text, bool paste);
|
||||
void setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode);
|
||||
|
||||
QByteArray serializeData();
|
||||
|
||||
private:
|
||||
void writePosition(QBuffer &buffer, const QRect &value);
|
||||
quint16 toFixedPoint16(float f);
|
||||
|
||||
private:
|
||||
struct ControlMsgData
|
||||
{
|
||||
ControlMsgType type = CMT_NULL;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
AndroidKeyeventAction action;
|
||||
AndroidKeycode keycode;
|
||||
AndroidMetastate metastate;
|
||||
} injectKeycode;
|
||||
struct
|
||||
{
|
||||
char *text = Q_NULLPTR;
|
||||
} injectText;
|
||||
struct
|
||||
{
|
||||
quint64 id;
|
||||
AndroidMotioneventAction action;
|
||||
AndroidMotioneventButtons buttons;
|
||||
QRect position;
|
||||
float pressure;
|
||||
} injectTouch;
|
||||
struct
|
||||
{
|
||||
QRect position;
|
||||
qint32 hScroll;
|
||||
qint32 vScroll;
|
||||
} injectScroll;
|
||||
struct
|
||||
{
|
||||
char *text = Q_NULLPTR;
|
||||
bool paste = true;
|
||||
} setClipboard;
|
||||
struct
|
||||
{
|
||||
ScreenPowerMode mode;
|
||||
} setScreenPowerMode;
|
||||
};
|
||||
|
||||
ControlMsgData() {}
|
||||
~ControlMsgData() {}
|
||||
};
|
||||
|
||||
ControlMsgData m_data;
|
||||
};
|
||||
|
||||
#endif // CONTROLMSG_H
|
17
QtScrcpy/device/controller/inputconvert/inputconvert.pri
Normal file
17
QtScrcpy/device/controller/inputconvert/inputconvert.pri
Normal file
|
@ -0,0 +1,17 @@
|
|||
HEADERS += \
|
||||
$$PWD/inputconvertbase.h \
|
||||
$$PWD/inputconvertgame.h \
|
||||
$$PWD/inputconvertnormal.h \
|
||||
$$PWD/controlmsg.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/inputconvertbase.cpp \
|
||||
$$PWD/inputconvertgame.cpp \
|
||||
$$PWD/inputconvertnormal.cpp \
|
||||
$$PWD/controlmsg.cpp
|
||||
|
||||
include ($$PWD/keymap/keymap.pri)
|
||||
|
||||
INCLUDEPATH += \
|
||||
$$PWD/keymap
|
||||
|
16
QtScrcpy/device/controller/inputconvert/inputconvertbase.cpp
Normal file
16
QtScrcpy/device/controller/inputconvert/inputconvertbase.cpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "inputconvertbase.h"
|
||||
#include "controller.h"
|
||||
|
||||
InputConvertBase::InputConvertBase(Controller *controller) : QObject(controller), m_controller(controller)
|
||||
{
|
||||
Q_ASSERT(controller);
|
||||
}
|
||||
|
||||
InputConvertBase::~InputConvertBase() {}
|
||||
|
||||
void InputConvertBase::sendControlMsg(ControlMsg *msg)
|
||||
{
|
||||
if (msg && m_controller) {
|
||||
m_controller->postControlMsg(msg);
|
||||
}
|
||||
}
|
35
QtScrcpy/device/controller/inputconvert/inputconvertbase.h
Normal file
35
QtScrcpy/device/controller/inputconvert/inputconvertbase.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef INPUTCONVERTBASE_H
|
||||
#define INPUTCONVERTBASE_H
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QPointer>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "controlmsg.h"
|
||||
|
||||
class Controller;
|
||||
class InputConvertBase : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
InputConvertBase(Controller *controller);
|
||||
virtual ~InputConvertBase();
|
||||
|
||||
// the frame size may be different from the real device size, so we need the size
|
||||
// to which the absolute position apply, to scale it accordingly
|
||||
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
|
||||
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
|
||||
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize) = 0;
|
||||
|
||||
signals:
|
||||
void grabCursor(bool grab);
|
||||
|
||||
protected:
|
||||
void sendControlMsg(ControlMsg *msg);
|
||||
|
||||
private:
|
||||
QPointer<Controller> m_controller;
|
||||
};
|
||||
|
||||
#endif // INPUTCONVERTBASE_H
|
495
QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp
Normal file
495
QtScrcpy/device/controller/inputconvert/inputconvertgame.cpp
Normal file
|
@ -0,0 +1,495 @@
|
|||
#include <QDebug>
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#include "inputconvertgame.h"
|
||||
|
||||
#define CURSOR_POS_CHECK 50
|
||||
|
||||
InputConvertGame::InputConvertGame(Controller *controller) : InputConvertNormal(controller) {}
|
||||
|
||||
InputConvertGame::~InputConvertGame() {}
|
||||
|
||||
void InputConvertGame::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
// 处理开关按键
|
||||
if (m_keyMap.isSwitchOnKeyboard() == false && m_keyMap.getSwitchKey() == static_cast<int>(from->button())) {
|
||||
if (from->type() != QEvent::MouseButtonPress) {
|
||||
return;
|
||||
}
|
||||
if (!switchGameMap()) {
|
||||
m_needBackMouseMove = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_needBackMouseMove && m_gameMap) {
|
||||
updateSize(frameSize, showSize);
|
||||
// mouse move
|
||||
if (m_keyMap.isValidMouseMoveMap()) {
|
||||
if (processMouseMove(from)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// mouse click
|
||||
if (processMouseClick(from)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
InputConvertNormal::mouseEvent(from, frameSize, showSize);
|
||||
}
|
||||
|
||||
void InputConvertGame::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (m_gameMap) {
|
||||
updateSize(frameSize, showSize);
|
||||
} else {
|
||||
InputConvertNormal::wheelEvent(from, frameSize, showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
// 处理开关按键
|
||||
if (m_keyMap.isSwitchOnKeyboard() && m_keyMap.getSwitchKey() == from->key()) {
|
||||
if (QEvent::KeyPress != from->type()) {
|
||||
return;
|
||||
}
|
||||
if (!switchGameMap()) {
|
||||
m_needBackMouseMove = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const KeyMap::KeyMapNode &node = m_keyMap.getKeyMapNodeKey(from->key());
|
||||
// 处理特殊按键:可以释放出鼠标的按键
|
||||
if (m_needBackMouseMove && KeyMap::KMT_CLICK == node.type && node.data.click.switchMap) {
|
||||
updateSize(frameSize, showSize);
|
||||
// Qt::Key_Tab Qt::Key_M for PUBG mobile
|
||||
processKeyClick(node.data.click.keyNode.pos, false, node.data.click.switchMap, from);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_gameMap) {
|
||||
updateSize(frameSize, showSize);
|
||||
if (!from || from->isAutoRepeat()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// small eyes
|
||||
if (from->key() == m_keyMap.getMouseMoveMap().data.mouseMove.smallEyes.key) {
|
||||
m_ctrlMouseMove.smallEyes = (QEvent::KeyPress == from->type());
|
||||
|
||||
if (QEvent::KeyPress == from->type()) {
|
||||
m_processMouseMove = false;
|
||||
int delay = 30;
|
||||
QTimer::singleShot(delay, this, [this]() { mouseMoveStopTouch(); });
|
||||
QTimer::singleShot(delay * 2, this, [this]() {
|
||||
mouseMoveStartTouch(nullptr);
|
||||
m_processMouseMove = true;
|
||||
});
|
||||
|
||||
stopMouseMoveTimer();
|
||||
} else {
|
||||
mouseMoveStopTouch();
|
||||
mouseMoveStartTouch(nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
// 处理方向盘
|
||||
case KeyMap::KMT_STEER_WHEEL:
|
||||
processSteerWheel(node, from);
|
||||
return;
|
||||
// 处理普通按键
|
||||
case KeyMap::KMT_CLICK:
|
||||
processKeyClick(node.data.click.keyNode.pos, false, node.data.click.switchMap, from);
|
||||
return;
|
||||
case KeyMap::KMT_CLICK_TWICE:
|
||||
processKeyClick(node.data.clickTwice.keyNode.pos, true, false, from);
|
||||
return;
|
||||
case KeyMap::KMT_DRAG:
|
||||
processKeyDrag(node.data.drag.keyNode.pos, node.data.drag.keyNode.extendPos, from);
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
InputConvertNormal::keyEvent(from, frameSize, showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::loadKeyMap(const QString &json)
|
||||
{
|
||||
m_keyMap.loadKeyMap(json);
|
||||
}
|
||||
|
||||
void InputConvertGame::updateSize(const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (showSize != m_showSize) {
|
||||
if (m_gameMap && m_keyMap.isValidMouseMoveMap()) {
|
||||
// show size change, resize grab cursor
|
||||
emit grabCursor(true);
|
||||
}
|
||||
}
|
||||
m_frameSize = frameSize;
|
||||
m_showSize = showSize;
|
||||
}
|
||||
|
||||
void InputConvertGame::sendTouchDownEvent(int id, QPointF pos)
|
||||
{
|
||||
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_DOWN);
|
||||
}
|
||||
|
||||
void InputConvertGame::sendTouchMoveEvent(int id, QPointF pos)
|
||||
{
|
||||
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_MOVE);
|
||||
}
|
||||
|
||||
void InputConvertGame::sendTouchUpEvent(int id, QPointF pos)
|
||||
{
|
||||
sendTouchEvent(id, pos, AMOTION_EVENT_ACTION_UP);
|
||||
}
|
||||
|
||||
void InputConvertGame::sendTouchEvent(int id, QPointF pos, AndroidMotioneventAction action)
|
||||
{
|
||||
if (0 > id || MULTI_TOUCH_MAX_NUM - 1 < id) {
|
||||
Q_ASSERT(0);
|
||||
return;
|
||||
}
|
||||
//qDebug() << "id:" << id << " pos:" << pos << " action" << action;
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setInjectTouchMsgData(
|
||||
static_cast<quint64>(id), action, static_cast<AndroidMotioneventButtons>(0), QRect(calcFrameAbsolutePos(pos).toPoint(), m_frameSize), 1.0f);
|
||||
sendControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
QPointF InputConvertGame::calcFrameAbsolutePos(QPointF relativePos)
|
||||
{
|
||||
QPointF absolutePos;
|
||||
absolutePos.setX(m_frameSize.width() * relativePos.x());
|
||||
absolutePos.setY(m_frameSize.height() * relativePos.y());
|
||||
return absolutePos;
|
||||
}
|
||||
|
||||
QPointF InputConvertGame::calcScreenAbsolutePos(QPointF relativePos)
|
||||
{
|
||||
QPointF absolutePos;
|
||||
absolutePos.setX(m_showSize.width() * relativePos.x());
|
||||
absolutePos.setY(m_showSize.height() * relativePos.y());
|
||||
return absolutePos;
|
||||
}
|
||||
|
||||
int InputConvertGame::attachTouchID(int key)
|
||||
{
|
||||
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
|
||||
if (0 == m_multiTouchID[i]) {
|
||||
m_multiTouchID[i] = key;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void InputConvertGame::detachTouchID(int key)
|
||||
{
|
||||
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
|
||||
if (key == m_multiTouchID[i]) {
|
||||
m_multiTouchID[i] = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int InputConvertGame::getTouchID(int key)
|
||||
{
|
||||
for (int i = 0; i < MULTI_TOUCH_MAX_NUM; i++) {
|
||||
if (key == m_multiTouchID[i]) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// -------- steer wheel event --------
|
||||
|
||||
void InputConvertGame::processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent *from)
|
||||
{
|
||||
int key = from->key();
|
||||
bool flag = from->type() == QEvent::KeyPress;
|
||||
// identify keys
|
||||
if (key == node.data.steerWheel.up.key) {
|
||||
m_ctrlSteerWheel.pressedUp = flag;
|
||||
} else if (key == node.data.steerWheel.right.key) {
|
||||
m_ctrlSteerWheel.pressedRight = flag;
|
||||
} else if (key == node.data.steerWheel.down.key) {
|
||||
m_ctrlSteerWheel.pressedDown = flag;
|
||||
} else { // left
|
||||
m_ctrlSteerWheel.pressedLeft = flag;
|
||||
}
|
||||
|
||||
// calc offset and pressed number
|
||||
QPointF offset(0.0, 0.0);
|
||||
int pressedNum = 0;
|
||||
if (m_ctrlSteerWheel.pressedUp) {
|
||||
++pressedNum;
|
||||
offset.ry() -= node.data.steerWheel.up.extendOffset;
|
||||
}
|
||||
if (m_ctrlSteerWheel.pressedRight) {
|
||||
++pressedNum;
|
||||
offset.rx() += node.data.steerWheel.right.extendOffset;
|
||||
}
|
||||
if (m_ctrlSteerWheel.pressedDown) {
|
||||
++pressedNum;
|
||||
offset.ry() += node.data.steerWheel.down.extendOffset;
|
||||
}
|
||||
if (m_ctrlSteerWheel.pressedLeft) {
|
||||
++pressedNum;
|
||||
offset.rx() -= node.data.steerWheel.left.extendOffset;
|
||||
}
|
||||
|
||||
// action
|
||||
if (pressedNum == 0) {
|
||||
// touch up release all
|
||||
int id = getTouchID(m_ctrlSteerWheel.touchKey);
|
||||
sendTouchUpEvent(id, node.data.steerWheel.centerPos + m_ctrlSteerWheel.lastOffset);
|
||||
detachTouchID(m_ctrlSteerWheel.touchKey);
|
||||
} else {
|
||||
int id;
|
||||
// first press, get key and touch down
|
||||
if (pressedNum == 1 && flag) {
|
||||
m_ctrlSteerWheel.touchKey = from->key();
|
||||
id = attachTouchID(m_ctrlSteerWheel.touchKey);
|
||||
sendTouchDownEvent(id, node.data.steerWheel.centerPos);
|
||||
} else {
|
||||
// jsut get touch id and move
|
||||
id = getTouchID(m_ctrlSteerWheel.touchKey);
|
||||
}
|
||||
sendTouchMoveEvent(id, node.data.steerWheel.centerPos + offset);
|
||||
}
|
||||
m_ctrlSteerWheel.lastOffset = offset;
|
||||
return;
|
||||
}
|
||||
|
||||
// -------- key event --------
|
||||
|
||||
void InputConvertGame::processKeyClick(const QPointF &clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from)
|
||||
{
|
||||
if (switchMap && QEvent::KeyRelease == from->type()) {
|
||||
m_needBackMouseMove = !m_needBackMouseMove;
|
||||
hideMouseCursor(!m_needBackMouseMove);
|
||||
}
|
||||
|
||||
if (QEvent::KeyPress == from->type()) {
|
||||
int id = attachTouchID(from->key());
|
||||
sendTouchDownEvent(id, clickPos);
|
||||
if (clickTwice) {
|
||||
sendTouchUpEvent(getTouchID(from->key()), clickPos);
|
||||
detachTouchID(from->key());
|
||||
}
|
||||
} else if (QEvent::KeyRelease == from->type()) {
|
||||
if (clickTwice) {
|
||||
int id = attachTouchID(from->key());
|
||||
sendTouchDownEvent(id, clickPos);
|
||||
}
|
||||
sendTouchUpEvent(getTouchID(from->key()), clickPos);
|
||||
detachTouchID(from->key());
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::processKeyDrag(const QPointF &startPos, QPointF endPos, const QKeyEvent *from)
|
||||
{
|
||||
if (QEvent::KeyPress == from->type()) {
|
||||
int id = attachTouchID(from->key());
|
||||
sendTouchDownEvent(id, startPos);
|
||||
sendTouchMoveEvent(id, endPos);
|
||||
}
|
||||
|
||||
if (QEvent::KeyRelease == from->type()) {
|
||||
int id = getTouchID(from->key());
|
||||
sendTouchUpEvent(id, endPos);
|
||||
detachTouchID(from->key());
|
||||
}
|
||||
}
|
||||
|
||||
// -------- mouse event --------
|
||||
|
||||
bool InputConvertGame::processMouseClick(const QMouseEvent *from)
|
||||
{
|
||||
const KeyMap::KeyMapNode &node = m_keyMap.getKeyMapNodeMouse(from->button());
|
||||
if (KeyMap::KMT_INVALID == node.type) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QEvent::MouseButtonPress == from->type() || QEvent::MouseButtonDblClick == from->type()) {
|
||||
int id = attachTouchID(from->button());
|
||||
sendTouchDownEvent(id, node.data.click.keyNode.pos);
|
||||
return true;
|
||||
}
|
||||
if (QEvent::MouseButtonRelease == from->type()) {
|
||||
int id = getTouchID(from->button());
|
||||
sendTouchUpEvent(id, node.data.click.keyNode.pos);
|
||||
detachTouchID(from->button());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputConvertGame::processMouseMove(const QMouseEvent *from)
|
||||
{
|
||||
if (QEvent::MouseMove != from->type()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkCursorPos(from)) {
|
||||
m_ctrlMouseMove.lastPos = QPointF(0.0, 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!m_ctrlMouseMove.lastPos.isNull() && m_processMouseMove) {
|
||||
QPointF distance = from->localPos() - m_ctrlMouseMove.lastPos;
|
||||
distance /= m_keyMap.getMouseMoveMap().data.mouseMove.speedRatio;
|
||||
|
||||
mouseMoveStartTouch(from);
|
||||
startMouseMoveTimer();
|
||||
|
||||
m_ctrlMouseMove.lastConverPos.setX(m_ctrlMouseMove.lastConverPos.x() + distance.x() / m_showSize.width());
|
||||
m_ctrlMouseMove.lastConverPos.setY(m_ctrlMouseMove.lastConverPos.y() + distance.y() / m_showSize.height());
|
||||
|
||||
if (m_ctrlMouseMove.lastConverPos.x() < 0.1 || m_ctrlMouseMove.lastConverPos.x() > 0.98 || m_ctrlMouseMove.lastConverPos.y() < 0.1
|
||||
|| m_ctrlMouseMove.lastConverPos.y() > 0.98) {
|
||||
if (m_ctrlMouseMove.smallEyes) {
|
||||
m_processMouseMove = false;
|
||||
int delay = 30;
|
||||
QTimer::singleShot(delay, this, [this]() { mouseMoveStopTouch(); });
|
||||
QTimer::singleShot(delay * 2, this, [this]() {
|
||||
mouseMoveStartTouch(nullptr);
|
||||
m_processMouseMove = true;
|
||||
});
|
||||
} else {
|
||||
mouseMoveStopTouch();
|
||||
mouseMoveStartTouch(from);
|
||||
}
|
||||
}
|
||||
|
||||
sendTouchMoveEvent(getTouchID(Qt::ExtraButton24), m_ctrlMouseMove.lastConverPos);
|
||||
}
|
||||
m_ctrlMouseMove.lastPos = from->localPos();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InputConvertGame::checkCursorPos(const QMouseEvent *from)
|
||||
{
|
||||
bool moveCursor = false;
|
||||
QPoint pos = from->pos();
|
||||
if (pos.x() < CURSOR_POS_CHECK) {
|
||||
pos.setX(m_showSize.width() - CURSOR_POS_CHECK);
|
||||
moveCursor = true;
|
||||
} else if (pos.x() > m_showSize.width() - CURSOR_POS_CHECK) {
|
||||
pos.setX(CURSOR_POS_CHECK);
|
||||
moveCursor = true;
|
||||
} else if (pos.y() < CURSOR_POS_CHECK) {
|
||||
pos.setY(m_showSize.height() - CURSOR_POS_CHECK);
|
||||
moveCursor = true;
|
||||
} else if (pos.y() > m_showSize.height() - CURSOR_POS_CHECK) {
|
||||
pos.setY(CURSOR_POS_CHECK);
|
||||
moveCursor = true;
|
||||
}
|
||||
|
||||
if (moveCursor) {
|
||||
moveCursorTo(from, pos);
|
||||
}
|
||||
|
||||
return moveCursor;
|
||||
}
|
||||
|
||||
void InputConvertGame::moveCursorTo(const QMouseEvent *from, const QPoint &localPosPixel)
|
||||
{
|
||||
QPoint posOffset = from->pos() - localPosPixel;
|
||||
QPoint globalPos = from->globalPos();
|
||||
globalPos -= posOffset;
|
||||
//qDebug()<<"move cursor to "<<globalPos<<" offset "<<posOffset;
|
||||
QCursor::setPos(globalPos);
|
||||
}
|
||||
|
||||
void InputConvertGame::mouseMoveStartTouch(const QMouseEvent *from)
|
||||
{
|
||||
Q_UNUSED(from)
|
||||
if (!m_ctrlMouseMove.touching) {
|
||||
QPointF mouseMoveStartPos
|
||||
= m_ctrlMouseMove.smallEyes ? m_keyMap.getMouseMoveMap().data.mouseMove.smallEyes.pos : m_keyMap.getMouseMoveMap().data.mouseMove.startPos;
|
||||
int id = attachTouchID(Qt::ExtraButton24);
|
||||
sendTouchDownEvent(id, mouseMoveStartPos);
|
||||
m_ctrlMouseMove.lastConverPos = mouseMoveStartPos;
|
||||
m_ctrlMouseMove.touching = true;
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::mouseMoveStopTouch()
|
||||
{
|
||||
if (m_ctrlMouseMove.touching) {
|
||||
sendTouchUpEvent(getTouchID(Qt::ExtraButton24), m_ctrlMouseMove.lastConverPos);
|
||||
detachTouchID(Qt::ExtraButton24);
|
||||
m_ctrlMouseMove.touching = false;
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::startMouseMoveTimer()
|
||||
{
|
||||
stopMouseMoveTimer();
|
||||
m_ctrlMouseMove.timer = startTimer(1000);
|
||||
}
|
||||
|
||||
void InputConvertGame::stopMouseMoveTimer()
|
||||
{
|
||||
if (0 != m_ctrlMouseMove.timer) {
|
||||
killTimer(m_ctrlMouseMove.timer);
|
||||
m_ctrlMouseMove.timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool InputConvertGame::switchGameMap()
|
||||
{
|
||||
m_gameMap = !m_gameMap;
|
||||
|
||||
if (!m_keyMap.isValidMouseMoveMap()) {
|
||||
return m_gameMap;
|
||||
}
|
||||
|
||||
// grab cursor and set cursor only mouse move map
|
||||
emit grabCursor(m_gameMap);
|
||||
hideMouseCursor(m_gameMap);
|
||||
|
||||
if (!m_gameMap) {
|
||||
stopMouseMoveTimer();
|
||||
mouseMoveStopTouch();
|
||||
}
|
||||
|
||||
return m_gameMap;
|
||||
}
|
||||
|
||||
void InputConvertGame::hideMouseCursor(bool hide)
|
||||
{
|
||||
if (hide) {
|
||||
#ifdef QT_NO_DEBUG
|
||||
QGuiApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
|
||||
#else
|
||||
QGuiApplication::setOverrideCursor(QCursor(Qt::CrossCursor));
|
||||
#endif
|
||||
} else {
|
||||
QGuiApplication::restoreOverrideCursor();
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
if (m_ctrlMouseMove.timer == event->timerId()) {
|
||||
stopMouseMoveTimer();
|
||||
mouseMoveStopTouch();
|
||||
}
|
||||
}
|
96
QtScrcpy/device/controller/inputconvert/inputconvertgame.h
Normal file
96
QtScrcpy/device/controller/inputconvert/inputconvertgame.h
Normal file
|
@ -0,0 +1,96 @@
|
|||
#ifndef INPUTCONVERTGAME_H
|
||||
#define INPUTCONVERTGAME_H
|
||||
|
||||
#include <QPointF>
|
||||
|
||||
#include "inputconvertnormal.h"
|
||||
#include "keymap.h"
|
||||
|
||||
#define MULTI_TOUCH_MAX_NUM 10
|
||||
class InputConvertGame : public InputConvertNormal
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
InputConvertGame(Controller *controller);
|
||||
virtual ~InputConvertGame();
|
||||
|
||||
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
|
||||
void loadKeyMap(const QString &json);
|
||||
|
||||
protected:
|
||||
void updateSize(const QSize &frameSize, const QSize &showSize);
|
||||
void sendTouchDownEvent(int id, QPointF pos);
|
||||
void sendTouchMoveEvent(int id, QPointF pos);
|
||||
void sendTouchUpEvent(int id, QPointF pos);
|
||||
void sendTouchEvent(int id, QPointF pos, AndroidMotioneventAction action);
|
||||
QPointF calcFrameAbsolutePos(QPointF relativePos);
|
||||
QPointF calcScreenAbsolutePos(QPointF relativePos);
|
||||
|
||||
// multi touch id
|
||||
int attachTouchID(int key);
|
||||
void detachTouchID(int key);
|
||||
int getTouchID(int key);
|
||||
|
||||
// steer wheel
|
||||
void processSteerWheel(const KeyMap::KeyMapNode &node, const QKeyEvent *from);
|
||||
|
||||
// click
|
||||
void processKeyClick(const QPointF &clickPos, bool clickTwice, bool switchMap, const QKeyEvent *from);
|
||||
|
||||
// drag
|
||||
void processKeyDrag(const QPointF &startPos, QPointF endPos, const QKeyEvent *from);
|
||||
|
||||
// mouse
|
||||
bool processMouseClick(const QMouseEvent *from);
|
||||
bool processMouseMove(const QMouseEvent *from);
|
||||
void moveCursorTo(const QMouseEvent *from, const QPoint &localPosPixel);
|
||||
void mouseMoveStartTouch(const QMouseEvent *from);
|
||||
void mouseMoveStopTouch();
|
||||
void startMouseMoveTimer();
|
||||
void stopMouseMoveTimer();
|
||||
|
||||
bool switchGameMap();
|
||||
bool checkCursorPos(const QMouseEvent *from);
|
||||
void hideMouseCursor(bool hide);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *event);
|
||||
|
||||
private:
|
||||
QSize m_frameSize;
|
||||
QSize m_showSize;
|
||||
bool m_gameMap = false;
|
||||
bool m_needBackMouseMove = false;
|
||||
int m_multiTouchID[MULTI_TOUCH_MAX_NUM] = { 0 };
|
||||
KeyMap m_keyMap;
|
||||
|
||||
bool m_processMouseMove = true;
|
||||
|
||||
// steer wheel
|
||||
struct
|
||||
{
|
||||
// the first key pressed
|
||||
int touchKey = Qt::Key_unknown;
|
||||
bool pressedUp = false;
|
||||
bool pressedDown = false;
|
||||
bool pressedLeft = false;
|
||||
bool pressedRight = false;
|
||||
// for last up
|
||||
QPointF lastOffset;
|
||||
} m_ctrlSteerWheel;
|
||||
|
||||
// mouse move
|
||||
struct
|
||||
{
|
||||
QPointF lastConverPos;
|
||||
QPointF lastPos = { 0.0, 0.0 };
|
||||
bool touching = false;
|
||||
int timer = 0;
|
||||
bool smallEyes = false;
|
||||
} m_ctrlMouseMove;
|
||||
};
|
||||
|
||||
#endif // INPUTCONVERTGAME_H
|
409
QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp
Normal file
409
QtScrcpy/device/controller/inputconvert/inputconvertnormal.cpp
Normal file
|
@ -0,0 +1,409 @@
|
|||
#include <cmath>
|
||||
|
||||
#include "inputconvertnormal.h"
|
||||
|
||||
InputConvertNormal::InputConvertNormal(Controller *controller) : InputConvertBase(controller) {}
|
||||
|
||||
InputConvertNormal::~InputConvertNormal() {}
|
||||
|
||||
void InputConvertNormal::mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (!from) {
|
||||
return;
|
||||
}
|
||||
|
||||
// action
|
||||
AndroidMotioneventAction action;
|
||||
switch (from->type()) {
|
||||
case QEvent::MouseButtonPress:
|
||||
action = AMOTION_EVENT_ACTION_DOWN;
|
||||
break;
|
||||
case QEvent::MouseButtonRelease:
|
||||
action = AMOTION_EVENT_ACTION_UP;
|
||||
break;
|
||||
case QEvent::MouseMove:
|
||||
// only support left button drag
|
||||
if (!(from->buttons() & Qt::LeftButton)) {
|
||||
return;
|
||||
}
|
||||
action = AMOTION_EVENT_ACTION_MOVE;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// pos
|
||||
QPointF pos = from->localPos();
|
||||
// convert pos
|
||||
pos.setX(pos.x() * frameSize.width() / showSize.width());
|
||||
pos.setY(pos.y() * frameSize.height() / showSize.height());
|
||||
|
||||
// set data
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_TOUCH);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setInjectTouchMsgData(
|
||||
static_cast<quint64>(POINTER_ID_MOUSE), action, convertMouseButtons(from->buttons()), QRect(pos.toPoint(), frameSize), 1.0f);
|
||||
sendControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (!from || 0 == from->delta()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// delta
|
||||
qint32 hScroll = 0;
|
||||
qint32 vScroll = 0;
|
||||
switch (from->orientation()) {
|
||||
case Qt::Horizontal:
|
||||
hScroll = from->delta() / abs(from->delta()) * 2;
|
||||
break;
|
||||
case Qt::Vertical:
|
||||
vScroll = from->delta() / abs(from->delta()) * 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// pos
|
||||
QPointF pos = from->posF();
|
||||
// convert pos
|
||||
pos.setX(pos.x() * frameSize.width() / showSize.width());
|
||||
pos.setY(pos.y() * frameSize.height() / showSize.height());
|
||||
|
||||
// set data
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_SCROLL);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setInjectScrollMsgData(QRect(pos.toPoint(), frameSize), hScroll, vScroll);
|
||||
sendControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void InputConvertNormal::keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
Q_UNUSED(frameSize)
|
||||
Q_UNUSED(showSize)
|
||||
if (!from) {
|
||||
return;
|
||||
}
|
||||
|
||||
// action
|
||||
AndroidKeyeventAction action;
|
||||
switch (from->type()) {
|
||||
case QEvent::KeyPress:
|
||||
action = AKEY_EVENT_ACTION_DOWN;
|
||||
break;
|
||||
case QEvent::KeyRelease:
|
||||
action = AKEY_EVENT_ACTION_UP;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// key code
|
||||
AndroidKeycode keyCode = convertKeyCode(from->key(), from->modifiers());
|
||||
if (AKEYCODE_UNKNOWN == keyCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set data
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setInjectKeycodeMsgData(action, keyCode, convertMetastate(from->modifiers()));
|
||||
sendControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
AndroidMotioneventButtons InputConvertNormal::convertMouseButtons(Qt::MouseButtons buttonState)
|
||||
{
|
||||
quint32 buttons = 0;
|
||||
if (buttonState & Qt::LeftButton) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_PRIMARY;
|
||||
}
|
||||
if (buttonState & Qt::RightButton) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_SECONDARY;
|
||||
}
|
||||
if (buttonState & Qt::MidButton) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_TERTIARY;
|
||||
}
|
||||
if (buttonState & Qt::XButton1) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_BACK;
|
||||
}
|
||||
if (buttonState & Qt::XButton2) {
|
||||
buttons |= AMOTION_EVENT_BUTTON_FORWARD;
|
||||
}
|
||||
return static_cast<AndroidMotioneventButtons>(buttons);
|
||||
}
|
||||
|
||||
AndroidKeycode InputConvertNormal::convertKeyCode(int key, Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
AndroidKeycode keyCode = AKEYCODE_UNKNOWN;
|
||||
// functional keys
|
||||
switch (key) {
|
||||
case Qt::Key_Return:
|
||||
keyCode = AKEYCODE_ENTER;
|
||||
break;
|
||||
case Qt::Key_Enter:
|
||||
keyCode = AKEYCODE_NUMPAD_ENTER;
|
||||
break;
|
||||
case Qt::Key_Escape:
|
||||
keyCode = AKEYCODE_ESCAPE;
|
||||
break;
|
||||
case Qt::Key_Backspace:
|
||||
keyCode = AKEYCODE_DEL;
|
||||
break;
|
||||
case Qt::Key_Delete:
|
||||
keyCode = AKEYCODE_FORWARD_DEL;
|
||||
break;
|
||||
case Qt::Key_Tab:
|
||||
keyCode = AKEYCODE_TAB;
|
||||
break;
|
||||
case Qt::Key_Home:
|
||||
keyCode = AKEYCODE_MOVE_HOME;
|
||||
break;
|
||||
case Qt::Key_End:
|
||||
keyCode = AKEYCODE_MOVE_END;
|
||||
break;
|
||||
case Qt::Key_PageUp:
|
||||
keyCode = AKEYCODE_PAGE_UP;
|
||||
break;
|
||||
case Qt::Key_PageDown:
|
||||
keyCode = AKEYCODE_PAGE_DOWN;
|
||||
break;
|
||||
case Qt::Key_Left:
|
||||
keyCode = AKEYCODE_DPAD_LEFT;
|
||||
break;
|
||||
case Qt::Key_Right:
|
||||
keyCode = AKEYCODE_DPAD_RIGHT;
|
||||
break;
|
||||
case Qt::Key_Up:
|
||||
keyCode = AKEYCODE_DPAD_UP;
|
||||
break;
|
||||
case Qt::Key_Down:
|
||||
keyCode = AKEYCODE_DPAD_DOWN;
|
||||
break;
|
||||
}
|
||||
if (AKEYCODE_UNKNOWN != keyCode) {
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
// if ALT and META are pressed, dont handle letters and space
|
||||
if (modifiers & (Qt::AltModifier | Qt::MetaModifier)) {
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
// character keys
|
||||
switch (key) {
|
||||
case Qt::Key_A:
|
||||
keyCode = AKEYCODE_A;
|
||||
break;
|
||||
case Qt::Key_B:
|
||||
keyCode = AKEYCODE_B;
|
||||
break;
|
||||
case Qt::Key_C:
|
||||
keyCode = AKEYCODE_C;
|
||||
break;
|
||||
case Qt::Key_D:
|
||||
keyCode = AKEYCODE_D;
|
||||
break;
|
||||
case Qt::Key_E:
|
||||
keyCode = AKEYCODE_E;
|
||||
break;
|
||||
case Qt::Key_F:
|
||||
keyCode = AKEYCODE_F;
|
||||
break;
|
||||
case Qt::Key_G:
|
||||
keyCode = AKEYCODE_G;
|
||||
break;
|
||||
case Qt::Key_H:
|
||||
keyCode = AKEYCODE_H;
|
||||
break;
|
||||
case Qt::Key_I:
|
||||
keyCode = AKEYCODE_I;
|
||||
break;
|
||||
case Qt::Key_J:
|
||||
keyCode = AKEYCODE_J;
|
||||
break;
|
||||
case Qt::Key_K:
|
||||
keyCode = AKEYCODE_K;
|
||||
break;
|
||||
case Qt::Key_L:
|
||||
keyCode = AKEYCODE_L;
|
||||
break;
|
||||
case Qt::Key_M:
|
||||
keyCode = AKEYCODE_M;
|
||||
break;
|
||||
case Qt::Key_N:
|
||||
keyCode = AKEYCODE_N;
|
||||
break;
|
||||
case Qt::Key_O:
|
||||
keyCode = AKEYCODE_O;
|
||||
break;
|
||||
case Qt::Key_P:
|
||||
keyCode = AKEYCODE_P;
|
||||
break;
|
||||
case Qt::Key_Q:
|
||||
keyCode = AKEYCODE_Q;
|
||||
break;
|
||||
case Qt::Key_R:
|
||||
keyCode = AKEYCODE_R;
|
||||
break;
|
||||
case Qt::Key_S:
|
||||
keyCode = AKEYCODE_S;
|
||||
break;
|
||||
case Qt::Key_T:
|
||||
keyCode = AKEYCODE_T;
|
||||
break;
|
||||
case Qt::Key_U:
|
||||
keyCode = AKEYCODE_U;
|
||||
break;
|
||||
case Qt::Key_V:
|
||||
keyCode = AKEYCODE_V;
|
||||
break;
|
||||
case Qt::Key_W:
|
||||
keyCode = AKEYCODE_W;
|
||||
break;
|
||||
case Qt::Key_X:
|
||||
keyCode = AKEYCODE_X;
|
||||
break;
|
||||
case Qt::Key_Y:
|
||||
keyCode = AKEYCODE_Y;
|
||||
break;
|
||||
case Qt::Key_Z:
|
||||
keyCode = AKEYCODE_Z;
|
||||
break;
|
||||
case Qt::Key_0:
|
||||
keyCode = AKEYCODE_0;
|
||||
break;
|
||||
case Qt::Key_1:
|
||||
case Qt::Key_Exclam:// !
|
||||
keyCode = AKEYCODE_1;
|
||||
break;
|
||||
case Qt::Key_2:
|
||||
keyCode = AKEYCODE_2;
|
||||
break;
|
||||
case Qt::Key_3:
|
||||
keyCode = AKEYCODE_3;
|
||||
break;
|
||||
case Qt::Key_4:
|
||||
case Qt::Key_Dollar://$
|
||||
keyCode = AKEYCODE_4;
|
||||
break;
|
||||
case Qt::Key_5:
|
||||
case Qt::Key_Percent:// %
|
||||
keyCode = AKEYCODE_5;
|
||||
break;
|
||||
case Qt::Key_6:
|
||||
case Qt::Key_AsciiCircum: //^
|
||||
keyCode = AKEYCODE_6;
|
||||
break;
|
||||
case Qt::Key_7:
|
||||
case Qt::Key_Ampersand: //&
|
||||
keyCode = AKEYCODE_7;
|
||||
break;
|
||||
case Qt::Key_8:
|
||||
keyCode = AKEYCODE_8;
|
||||
break;
|
||||
case Qt::Key_9:
|
||||
keyCode = AKEYCODE_9;
|
||||
break;
|
||||
case Qt::Key_Space:
|
||||
keyCode = AKEYCODE_SPACE;
|
||||
break;
|
||||
case Qt::Key_Comma://,
|
||||
case Qt::Key_Less://<
|
||||
keyCode = AKEYCODE_COMMA;
|
||||
break;
|
||||
case Qt::Key_Period://.
|
||||
case Qt::Key_Greater://>
|
||||
keyCode = AKEYCODE_PERIOD;
|
||||
break;
|
||||
case Qt::Key_Minus://-
|
||||
case Qt::Key_Underscore: //_
|
||||
keyCode = AKEYCODE_MINUS;
|
||||
break;
|
||||
case Qt::Key_Equal://=
|
||||
keyCode = AKEYCODE_EQUALS;
|
||||
break;
|
||||
case Qt::Key_BracketLeft://[
|
||||
case Qt::Key_BraceLeft: //{
|
||||
keyCode = AKEYCODE_LEFT_BRACKET;
|
||||
break;
|
||||
case Qt::Key_BracketRight://]
|
||||
case Qt::Key_BraceRight: //}
|
||||
keyCode = AKEYCODE_RIGHT_BRACKET;
|
||||
break;
|
||||
case Qt::Key_Backslash:// \ ????
|
||||
case Qt::Key_Bar: //|
|
||||
keyCode = AKEYCODE_BACKSLASH;
|
||||
break;
|
||||
case Qt::Key_Semicolon://;
|
||||
case Qt::Key_Colon: //:
|
||||
keyCode = AKEYCODE_SEMICOLON;
|
||||
break;
|
||||
case Qt::Key_Apostrophe://'
|
||||
case Qt::Key_QuoteDbl: //"
|
||||
keyCode = AKEYCODE_APOSTROPHE;
|
||||
break;
|
||||
case Qt::Key_Slash:// /
|
||||
case Qt::Key_Question://?
|
||||
keyCode = AKEYCODE_SLASH;
|
||||
break;
|
||||
case Qt::Key_At://@
|
||||
keyCode = AKEYCODE_AT;
|
||||
break;
|
||||
case Qt::Key_Plus://+
|
||||
keyCode = AKEYCODE_PLUS;
|
||||
break;
|
||||
case Qt::Key_QuoteLeft://`
|
||||
case Qt::Key_AsciiTilde://~
|
||||
keyCode = AKEYCODE_GRAVE;
|
||||
break;
|
||||
case Qt::Key_NumberSign: //#
|
||||
keyCode = AKEYCODE_POUND;
|
||||
break;
|
||||
case Qt::Key_ParenLeft: //(
|
||||
keyCode = AKEYCODE_NUMPAD_LEFT_PAREN;
|
||||
break;
|
||||
case Qt::Key_ParenRight: //)
|
||||
keyCode = AKEYCODE_NUMPAD_RIGHT_PAREN;
|
||||
break;
|
||||
case Qt::Key_Asterisk: //*
|
||||
keyCode = AKEYCODE_STAR;
|
||||
break;
|
||||
}
|
||||
return keyCode;
|
||||
}
|
||||
|
||||
AndroidMetastate InputConvertNormal::convertMetastate(Qt::KeyboardModifiers modifiers)
|
||||
{
|
||||
int metastate = AMETA_NONE;
|
||||
|
||||
if (modifiers & Qt::ShiftModifier) {
|
||||
metastate |= AMETA_SHIFT_ON;
|
||||
}
|
||||
if (modifiers & Qt::ControlModifier) {
|
||||
metastate |= AMETA_CTRL_ON;
|
||||
}
|
||||
if (modifiers & Qt::AltModifier) {
|
||||
metastate |= AMETA_ALT_ON;
|
||||
}
|
||||
if (modifiers & Qt::MetaModifier) {
|
||||
metastate |= AMETA_META_ON;
|
||||
}
|
||||
/*
|
||||
if (mod & KMOD_NUM) {
|
||||
metastate |= AMETA_NUM_LOCK_ON;
|
||||
}
|
||||
if (mod & KMOD_CAPS) {
|
||||
metastate |= AMETA_CAPS_LOCK_ON;
|
||||
}
|
||||
if (mod & KMOD_MODE) { // Alt Gr
|
||||
// no mapping?
|
||||
}
|
||||
*/
|
||||
return static_cast<AndroidMetastate>(metastate);
|
||||
}
|
23
QtScrcpy/device/controller/inputconvert/inputconvertnormal.h
Normal file
23
QtScrcpy/device/controller/inputconvert/inputconvertnormal.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#ifndef INPUTCONVERT_H
|
||||
#define INPUTCONVERT_H
|
||||
|
||||
#include "inputconvertbase.h"
|
||||
|
||||
class InputConvertNormal : public InputConvertBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
InputConvertNormal(Controller *controller);
|
||||
virtual ~InputConvertNormal();
|
||||
|
||||
virtual void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
virtual void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
virtual void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
|
||||
private:
|
||||
AndroidMotioneventButtons convertMouseButtons(Qt::MouseButtons buttonState);
|
||||
AndroidKeycode convertKeyCode(int key, Qt::KeyboardModifiers modifiers);
|
||||
AndroidMetastate convertMetastate(Qt::KeyboardModifiers modifiers);
|
||||
};
|
||||
|
||||
#endif // INPUTCONVERT_H
|
428
QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp
Normal file
428
QtScrcpy/device/controller/inputconvert/keymap/keymap.cpp
Normal file
|
@ -0,0 +1,428 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QMetaEnum>
|
||||
|
||||
#include "keymap.h"
|
||||
|
||||
QString KeyMap::s_keyMapPath = "";
|
||||
|
||||
KeyMap::KeyMap(QObject *parent) : QObject(parent) {}
|
||||
|
||||
KeyMap::~KeyMap() {}
|
||||
|
||||
const QString &KeyMap::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;
|
||||
}
|
||||
|
||||
void KeyMap::loadKeyMap(const QString &json)
|
||||
{
|
||||
QString errorString;
|
||||
QJsonParseError jsonError;
|
||||
QJsonDocument jsonDoc;
|
||||
QJsonObject rootObj;
|
||||
QPair<ActionType, int> switchKey;
|
||||
|
||||
jsonDoc = QJsonDocument::fromJson(json.toUtf8(), &jsonError);
|
||||
|
||||
if (jsonError.error != QJsonParseError::NoError) {
|
||||
errorString = QString("json error: %1").arg(jsonError.errorString());
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
// switchKey
|
||||
rootObj = jsonDoc.object();
|
||||
|
||||
if (!checkItemString(rootObj, "switchKey")) {
|
||||
errorString = QString("json error: no find switchKey");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
switchKey = getItemKey(rootObj, "switchKey");
|
||||
if (switchKey.first == AT_INVALID) {
|
||||
errorString = QString("json error: switchKey invalid");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
m_switchKey.type = switchKey.first;
|
||||
m_switchKey.key = switchKey.second;
|
||||
|
||||
// mouseMoveMap
|
||||
if (checkItemObject(rootObj, "mouseMoveMap")) {
|
||||
QJsonObject mouseMoveMap = getItemObject(rootObj, "mouseMoveMap");
|
||||
KeyMapNode keyMapNode;
|
||||
keyMapNode.type = KMT_MOUSE_MOVE;
|
||||
|
||||
if (!checkItemDouble(mouseMoveMap, "speedRatio")) {
|
||||
errorString = QString("json error: mouseMoveMap on find speedRatio");
|
||||
goto parseError;
|
||||
}
|
||||
keyMapNode.data.mouseMove.speedRatio = static_cast<int>(getItemDouble(mouseMoveMap, "speedRatio"));
|
||||
|
||||
if (!checkItemObject(mouseMoveMap, "startPos")) {
|
||||
errorString = QString("json error: mouseMoveMap on find startPos");
|
||||
goto parseError;
|
||||
}
|
||||
QJsonObject startPos = mouseMoveMap.value("startPos").toObject();
|
||||
if (checkItemDouble(startPos, "x")) {
|
||||
keyMapNode.data.mouseMove.startPos.setX(getItemDouble(startPos, "x"));
|
||||
}
|
||||
if (checkItemDouble(startPos, "y")) {
|
||||
keyMapNode.data.mouseMove.startPos.setY(getItemDouble(startPos, "y"));
|
||||
}
|
||||
|
||||
// small eyes
|
||||
if (checkItemObject(mouseMoveMap, "smallEyes")) {
|
||||
QJsonObject smallEyes = mouseMoveMap.value("smallEyes").toObject();
|
||||
if (!smallEyes.contains("type") || !smallEyes.value("type").isString()) {
|
||||
errorString = QString("json error: smallEyes no find node type");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
// type just support KMT_CLICK
|
||||
KeyMap::KeyMapType type = getItemKeyMapType(smallEyes, "type");
|
||||
if (KeyMap::KMT_CLICK != type) {
|
||||
errorString = QString("json error: smallEyes just support KMT_CLICK");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
// safe check
|
||||
if (!checkForClick(smallEyes)) {
|
||||
errorString = QString("json error: smallEyes node format error");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
QPair<ActionType, int> key = getItemKey(smallEyes, "key");
|
||||
if (key.first == AT_INVALID) {
|
||||
errorString = QString("json error: keyMapNodes node invalid key: %1").arg(smallEyes.value("key").toString());
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
keyMapNode.data.mouseMove.smallEyes.type = key.first;
|
||||
keyMapNode.data.mouseMove.smallEyes.key = key.second;
|
||||
keyMapNode.data.mouseMove.smallEyes.pos = getItemPos(smallEyes, "pos");
|
||||
}
|
||||
|
||||
m_idxMouseMove = m_keyMapNodes.size();
|
||||
m_keyMapNodes.push_back(keyMapNode);
|
||||
}
|
||||
|
||||
// keyMapNodes
|
||||
if (rootObj.contains("keyMapNodes") && rootObj.value("keyMapNodes").isArray()) {
|
||||
QJsonArray keyMapNodes = rootObj.value("keyMapNodes").toArray();
|
||||
QJsonObject node;
|
||||
int size = keyMapNodes.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!keyMapNodes.at(i).isObject()) {
|
||||
errorString = QString("json error: keyMapNodes node must be json object");
|
||||
goto parseError;
|
||||
}
|
||||
node = keyMapNodes.at(i).toObject();
|
||||
if (!node.contains("type") || !node.value("type").isString()) {
|
||||
errorString = QString("json error: keyMapNodes no find node type");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
KeyMap::KeyMapType type = getItemKeyMapType(node, "type");
|
||||
switch (type) {
|
||||
case KeyMap::KMT_CLICK: {
|
||||
// safe check
|
||||
if (!checkForClick(node)) {
|
||||
qWarning() << "json error: keyMapNodes node format error";
|
||||
break;
|
||||
}
|
||||
QPair<ActionType, int> key = getItemKey(node, "key");
|
||||
if (key.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
|
||||
break;
|
||||
}
|
||||
KeyMapNode keyMapNode;
|
||||
keyMapNode.type = type;
|
||||
keyMapNode.data.click.keyNode.type = key.first;
|
||||
keyMapNode.data.click.keyNode.key = key.second;
|
||||
keyMapNode.data.click.keyNode.pos = getItemPos(node, "pos");
|
||||
keyMapNode.data.click.switchMap = getItemBool(node, "switchMap");
|
||||
m_keyMapNodes.push_back(keyMapNode);
|
||||
} break;
|
||||
case KeyMap::KMT_CLICK_TWICE: {
|
||||
// safe check
|
||||
if (!checkForClickTwice(node)) {
|
||||
qWarning() << "json error: keyMapNodes node format error";
|
||||
break;
|
||||
}
|
||||
|
||||
QPair<ActionType, int> key = getItemKey(node, "key");
|
||||
if (key.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
|
||||
break;
|
||||
}
|
||||
KeyMapNode keyMapNode;
|
||||
keyMapNode.type = type;
|
||||
keyMapNode.data.click.keyNode.type = key.first;
|
||||
keyMapNode.data.click.keyNode.key = key.second;
|
||||
keyMapNode.data.click.keyNode.pos = getItemPos(node, "pos");
|
||||
keyMapNode.data.click.switchMap = getItemBool(node, "switchMap");
|
||||
m_keyMapNodes.push_back(keyMapNode);
|
||||
} break;
|
||||
case KeyMap::KMT_STEER_WHEEL: {
|
||||
// safe check
|
||||
if (!checkForSteerWhell(node)) {
|
||||
qWarning() << "json error: keyMapNodes node format error";
|
||||
break;
|
||||
}
|
||||
QPair<ActionType, int> leftKey = getItemKey(node, "leftKey");
|
||||
QPair<ActionType, int> rightKey = getItemKey(node, "rightKey");
|
||||
QPair<ActionType, int> upKey = getItemKey(node, "upKey");
|
||||
QPair<ActionType, int> downKey = getItemKey(node, "downKey");
|
||||
if (leftKey.first == AT_INVALID || rightKey.first == AT_INVALID || upKey.first == AT_INVALID || downKey.first == AT_INVALID) {
|
||||
if (leftKey.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("leftKey").toString();
|
||||
}
|
||||
if (rightKey.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("rightKey").toString();
|
||||
}
|
||||
if (upKey.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("upKey").toString();
|
||||
}
|
||||
if (downKey.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("downKey").toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
KeyMapNode keyMapNode;
|
||||
keyMapNode.type = type;
|
||||
|
||||
keyMapNode.data.steerWheel.left = { leftKey.first, leftKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "leftOffset") };
|
||||
keyMapNode.data.steerWheel.right = { rightKey.first, rightKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "rightOffset") };
|
||||
keyMapNode.data.steerWheel.up = { upKey.first, upKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "upOffset") };
|
||||
keyMapNode.data.steerWheel.down = { downKey.first, downKey.second, QPointF(0, 0), QPointF(0, 0), getItemDouble(node, "downOffset") };
|
||||
|
||||
keyMapNode.data.steerWheel.centerPos = getItemPos(node, "centerPos");
|
||||
m_idxSteerWheel = m_keyMapNodes.size();
|
||||
m_keyMapNodes.push_back(keyMapNode);
|
||||
} break;
|
||||
case KeyMap::KMT_DRAG: {
|
||||
// safe check
|
||||
if (!checkForDrag(node)) {
|
||||
qWarning() << "json error: keyMapNodes node format error";
|
||||
break;
|
||||
}
|
||||
|
||||
QPair<ActionType, int> key = getItemKey(node, "key");
|
||||
if (key.first == AT_INVALID) {
|
||||
qWarning() << "json error: keyMapNodes node invalid key: " << node.value("key").toString();
|
||||
break;
|
||||
}
|
||||
KeyMapNode keyMapNode;
|
||||
keyMapNode.type = type;
|
||||
keyMapNode.data.drag.keyNode.type = key.first;
|
||||
keyMapNode.data.drag.keyNode.key = key.second;
|
||||
keyMapNode.data.drag.keyNode.pos = getItemPos(node, "startPos");
|
||||
keyMapNode.data.drag.keyNode.extendPos = getItemPos(node, "endPos");
|
||||
m_keyMapNodes.push_back(keyMapNode);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning() << "json error: keyMapNodes invalid node type:" << node.value("type").toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// this must be called after m_keyMapNodes is stable
|
||||
makeReverseMap();
|
||||
qInfo() << "Script updated.";
|
||||
|
||||
parseError:
|
||||
if (!errorString.isEmpty()) {
|
||||
qWarning() << errorString;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const KeyMap::KeyMapNode &KeyMap::getKeyMapNode(int key)
|
||||
{
|
||||
auto p = m_rmapKey.value(key, &m_invalidNode);
|
||||
if (p == &m_invalidNode) {
|
||||
return *m_rmapMouse.value(key, &m_invalidNode);
|
||||
}
|
||||
return *p;
|
||||
}
|
||||
|
||||
const KeyMap::KeyMapNode &KeyMap::getKeyMapNodeKey(int key)
|
||||
{
|
||||
return *m_rmapKey.value(key, &m_invalidNode);
|
||||
}
|
||||
|
||||
const KeyMap::KeyMapNode &KeyMap::getKeyMapNodeMouse(int key)
|
||||
{
|
||||
return *m_rmapMouse.value(key, &m_invalidNode);
|
||||
}
|
||||
|
||||
bool KeyMap::isSwitchOnKeyboard()
|
||||
{
|
||||
return m_switchKey.type == AT_KEY;
|
||||
}
|
||||
|
||||
int KeyMap::getSwitchKey()
|
||||
{
|
||||
return m_switchKey.key;
|
||||
}
|
||||
|
||||
const KeyMap::KeyMapNode &KeyMap::getMouseMoveMap()
|
||||
{
|
||||
return m_keyMapNodes[m_idxMouseMove];
|
||||
}
|
||||
|
||||
bool KeyMap::isValidMouseMoveMap()
|
||||
{
|
||||
return m_idxMouseMove != -1;
|
||||
}
|
||||
|
||||
bool KeyMap::isValidSteerWheelMap()
|
||||
{
|
||||
return m_idxSteerWheel != -1;
|
||||
}
|
||||
|
||||
void KeyMap::makeReverseMap()
|
||||
{
|
||||
m_rmapKey.clear();
|
||||
m_rmapMouse.clear();
|
||||
for (int i = 0; i < m_keyMapNodes.size(); ++i) {
|
||||
auto &node = m_keyMapNodes[i];
|
||||
switch (node.type) {
|
||||
case KMT_CLICK: {
|
||||
QMultiHash<int, KeyMapNode *> &m = node.data.click.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
m.insert(node.data.click.keyNode.key, &node);
|
||||
} break;
|
||||
case KMT_CLICK_TWICE: {
|
||||
QMultiHash<int, KeyMapNode *> &m = node.data.clickTwice.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
m.insert(node.data.clickTwice.keyNode.key, &node);
|
||||
} break;
|
||||
case KMT_STEER_WHEEL: {
|
||||
QMultiHash<int, KeyMapNode *> &ml = node.data.steerWheel.left.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
ml.insert(node.data.steerWheel.left.key, &node);
|
||||
QMultiHash<int, KeyMapNode *> &mr = node.data.steerWheel.right.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
mr.insert(node.data.steerWheel.right.key, &node);
|
||||
QMultiHash<int, KeyMapNode *> &mu = node.data.steerWheel.up.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
mu.insert(node.data.steerWheel.up.key, &node);
|
||||
QMultiHash<int, KeyMapNode *> &md = node.data.steerWheel.down.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
md.insert(node.data.steerWheel.down.key, &node);
|
||||
} break;
|
||||
case KMT_DRAG: {
|
||||
QMultiHash<int, KeyMapNode *> &m = node.data.drag.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
m.insert(node.data.drag.keyNode.key, &node);
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString KeyMap::getItemString(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.value(name).toString();
|
||||
}
|
||||
|
||||
double KeyMap::getItemDouble(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.value(name).toDouble();
|
||||
}
|
||||
|
||||
bool KeyMap::getItemBool(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.value(name).toBool(false);
|
||||
}
|
||||
|
||||
QJsonObject KeyMap::getItemObject(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.value(name).toObject();
|
||||
}
|
||||
|
||||
QPointF KeyMap::getItemPos(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
QJsonObject pos = node.value(name).toObject();
|
||||
return QPointF(pos.value("x").toDouble(), pos.value("y").toDouble());
|
||||
}
|
||||
|
||||
QPair<KeyMap::ActionType, int> KeyMap::getItemKey(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
QString value = getItemString(node, name);
|
||||
int key = m_metaEnumKey.keyToValue(value.toStdString().c_str());
|
||||
int btn = m_metaEnumMouseButtons.keyToValue(value.toStdString().c_str());
|
||||
if (key == -1 && btn == -1) {
|
||||
return { AT_INVALID, -1 };
|
||||
} else if (key != -1) {
|
||||
return { AT_KEY, key };
|
||||
} else {
|
||||
return { AT_MOUSE, btn };
|
||||
}
|
||||
}
|
||||
|
||||
KeyMap::KeyMapType KeyMap::getItemKeyMapType(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
QString value = getItemString(node, name);
|
||||
return static_cast<KeyMap::KeyMapType>(m_metaEnumKeyMapType.keyToValue(value.toStdString().c_str()));
|
||||
}
|
||||
|
||||
bool KeyMap::checkItemString(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.contains(name) && node.value(name).isString();
|
||||
}
|
||||
|
||||
bool KeyMap::checkItemDouble(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.contains(name) && node.value(name).isDouble();
|
||||
}
|
||||
|
||||
bool KeyMap::checkItemBool(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.contains(name) && node.value(name).isBool();
|
||||
}
|
||||
|
||||
bool KeyMap::checkItemObject(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
return node.contains(name) && node.value(name).isObject();
|
||||
}
|
||||
|
||||
bool KeyMap::checkItemPos(const QJsonObject &node, const QString &name)
|
||||
{
|
||||
if (node.contains(name) && node.value(name).isObject()) {
|
||||
QJsonObject pos = node.value(name).toObject();
|
||||
return pos.contains("x") && pos.value("x").isDouble() && pos.contains("y") && pos.value("y").isDouble();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool KeyMap::checkForClick(const QJsonObject &node)
|
||||
{
|
||||
return checkForClickTwice(node) && checkItemBool(node, "switchMap");
|
||||
}
|
||||
|
||||
bool KeyMap::checkForClickTwice(const QJsonObject &node)
|
||||
{
|
||||
return checkItemString(node, "key") && checkItemPos(node, "pos");
|
||||
}
|
||||
|
||||
bool KeyMap::checkForSteerWhell(const QJsonObject &node)
|
||||
{
|
||||
return checkItemString(node, "leftKey") && checkItemString(node, "rightKey") && checkItemString(node, "upKey") && checkItemString(node, "downKey")
|
||||
&& checkItemDouble(node, "leftOffset") && checkItemDouble(node, "rightOffset") && checkItemDouble(node, "upOffset")
|
||||
&& checkItemDouble(node, "downOffset") && checkItemPos(node, "centerPos");
|
||||
}
|
||||
|
||||
bool KeyMap::checkForDrag(const QJsonObject &node)
|
||||
{
|
||||
return checkItemString(node, "key") && checkItemPos(node, "startPos") && checkItemPos(node, "endPos");
|
||||
}
|
156
QtScrcpy/device/controller/inputconvert/keymap/keymap.h
Normal file
156
QtScrcpy/device/controller/inputconvert/keymap/keymap.h
Normal file
|
@ -0,0 +1,156 @@
|
|||
#ifndef KEYMAP_H
|
||||
#define KEYMAP_H
|
||||
#include <QJsonObject>
|
||||
#include <QMetaEnum>
|
||||
#include <QMultiHash>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QVector>
|
||||
|
||||
class KeyMap : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum KeyMapType
|
||||
{
|
||||
KMT_INVALID = -1,
|
||||
KMT_CLICK = 0,
|
||||
KMT_CLICK_TWICE,
|
||||
KMT_STEER_WHEEL,
|
||||
KMT_DRAG,
|
||||
KMT_MOUSE_MOVE
|
||||
};
|
||||
Q_ENUM(KeyMapType)
|
||||
|
||||
enum ActionType
|
||||
{
|
||||
AT_INVALID = -1,
|
||||
AT_KEY = 0,
|
||||
AT_MOUSE = 1,
|
||||
};
|
||||
Q_ENUM(ActionType)
|
||||
|
||||
struct KeyNode
|
||||
{
|
||||
ActionType type = AT_INVALID;
|
||||
int key = Qt::Key_unknown;
|
||||
QPointF pos = QPointF(0, 0); // normal key
|
||||
QPointF extendPos = QPointF(0, 0); // for drag
|
||||
double extendOffset = 0.0; // for steerWheel
|
||||
|
||||
KeyNode(
|
||||
ActionType type = AT_INVALID,
|
||||
int key = Qt::Key_unknown,
|
||||
QPointF pos = QPointF(0, 0),
|
||||
QPointF extendPos = QPointF(0, 0),
|
||||
double extendOffset = 0.0)
|
||||
: type(type), key(key), pos(pos), extendPos(extendPos), extendOffset(extendOffset)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
struct KeyMapNode
|
||||
{
|
||||
KeyMapType type = KMT_INVALID;
|
||||
union DATA
|
||||
{
|
||||
struct
|
||||
{
|
||||
KeyNode keyNode;
|
||||
bool switchMap = false;
|
||||
} click;
|
||||
struct
|
||||
{
|
||||
KeyNode keyNode;
|
||||
} clickTwice;
|
||||
struct
|
||||
{
|
||||
QPointF centerPos = { 0.0, 0.0 };
|
||||
KeyNode left, right, up, down;
|
||||
} steerWheel;
|
||||
struct
|
||||
{
|
||||
KeyNode keyNode;
|
||||
} drag;
|
||||
struct
|
||||
{
|
||||
QPointF startPos = { 0.0, 0.0 };
|
||||
int speedRatio = 1;
|
||||
KeyNode smallEyes;
|
||||
} mouseMove;
|
||||
DATA() {}
|
||||
~DATA() {}
|
||||
} data;
|
||||
KeyMapNode() {}
|
||||
~KeyMapNode() {}
|
||||
};
|
||||
|
||||
KeyMap(QObject *parent = Q_NULLPTR);
|
||||
virtual ~KeyMap();
|
||||
|
||||
void loadKeyMap(const QString &json);
|
||||
const KeyMap::KeyMapNode &getKeyMapNode(int key);
|
||||
const KeyMap::KeyMapNode &getKeyMapNodeKey(int key);
|
||||
const KeyMap::KeyMapNode &getKeyMapNodeMouse(int key);
|
||||
bool isSwitchOnKeyboard();
|
||||
int getSwitchKey();
|
||||
|
||||
bool isValidMouseMoveMap();
|
||||
bool isValidSteerWheelMap();
|
||||
const KeyMap::KeyMapNode &getMouseMoveMap();
|
||||
|
||||
static const QString &getKeyMapPath();
|
||||
|
||||
private:
|
||||
// set up the reverse map from key/event event to keyMapNode
|
||||
void makeReverseMap();
|
||||
|
||||
// safe check for base
|
||||
bool checkItemString(const QJsonObject &node, const QString &name);
|
||||
bool checkItemDouble(const QJsonObject &node, const QString &name);
|
||||
bool checkItemBool(const QJsonObject &node, const QString &name);
|
||||
bool checkItemObject(const QJsonObject &node, const QString &name);
|
||||
bool checkItemPos(const QJsonObject &node, const QString &name);
|
||||
|
||||
// safe check for KeyMapNode
|
||||
bool checkForClick(const QJsonObject &node);
|
||||
bool checkForClickTwice(const QJsonObject &node);
|
||||
bool checkForSteerWhell(const QJsonObject &node);
|
||||
bool checkForDrag(const QJsonObject &node);
|
||||
|
||||
// get keymap from json object
|
||||
QString getItemString(const QJsonObject &node, const QString &name);
|
||||
double getItemDouble(const QJsonObject &node, const QString &name);
|
||||
bool getItemBool(const QJsonObject &node, const QString &name);
|
||||
QJsonObject getItemObject(const QJsonObject &node, const QString &name);
|
||||
QPointF getItemPos(const QJsonObject &node, const QString &name);
|
||||
QPair<ActionType, int> getItemKey(const QJsonObject &node, const QString &name);
|
||||
KeyMapType getItemKeyMapType(const QJsonObject &node, const QString &name);
|
||||
|
||||
private:
|
||||
static QString s_keyMapPath;
|
||||
|
||||
QVector<KeyMapNode> m_keyMapNodes;
|
||||
KeyNode m_switchKey = { AT_KEY, Qt::Key_QuoteLeft };
|
||||
|
||||
// just for return
|
||||
KeyMapNode m_invalidNode;
|
||||
|
||||
// steer wheel index
|
||||
int m_idxSteerWheel = -1;
|
||||
|
||||
// mouse move index
|
||||
int m_idxMouseMove = -1;
|
||||
|
||||
// mapping of key/mouse event name to index
|
||||
QMetaEnum m_metaEnumKey = QMetaEnum::fromType<Qt::Key>();
|
||||
QMetaEnum m_metaEnumMouseButtons = QMetaEnum::fromType<Qt::MouseButtons>();
|
||||
QMetaEnum m_metaEnumKeyMapType = QMetaEnum::fromType<KeyMap::KeyMapType>();
|
||||
// reverse map of key/mouse event
|
||||
QMultiHash<int, KeyMapNode *> m_rmapKey;
|
||||
QMultiHash<int, KeyMapNode *> m_rmapMouse;
|
||||
};
|
||||
|
||||
#endif // KEYMAP_H
|
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/keymap.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/keymap.cpp
|
66
QtScrcpy/device/controller/receiver/devicemsg.cpp
Normal file
66
QtScrcpy/device/controller/receiver/devicemsg.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "bufferutil.h"
|
||||
#include "devicemsg.h"
|
||||
|
||||
DeviceMsg::DeviceMsg(QObject *parent) : QObject(parent) {}
|
||||
|
||||
DeviceMsg::~DeviceMsg()
|
||||
{
|
||||
if (DMT_GET_CLIPBOARD == m_data.type && Q_NULLPTR != m_data.clipboardMsg.text) {
|
||||
delete m_data.clipboardMsg.text;
|
||||
m_data.clipboardMsg.text = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
|
||||
DeviceMsg::DeviceMsgType DeviceMsg::type()
|
||||
{
|
||||
return m_data.type;
|
||||
}
|
||||
|
||||
void DeviceMsg::getClipboardMsgData(QString &text)
|
||||
{
|
||||
text = QString::fromUtf8(m_data.clipboardMsg.text);
|
||||
}
|
||||
|
||||
qint32 DeviceMsg::deserialize(QByteArray &byteArray)
|
||||
{
|
||||
QBuffer buf(&byteArray);
|
||||
buf.open(QBuffer::ReadOnly);
|
||||
|
||||
qint64 len = buf.size();
|
||||
char c = 0;
|
||||
qint32 ret = 0;
|
||||
|
||||
if (len < 3) {
|
||||
// at least type + empty string length
|
||||
return 0; // not available
|
||||
}
|
||||
|
||||
buf.getChar(&c);
|
||||
m_data.type = (DeviceMsgType)c;
|
||||
switch (m_data.type) {
|
||||
case DMT_GET_CLIPBOARD: {
|
||||
m_data.clipboardMsg.text = Q_NULLPTR;
|
||||
quint16 clipboardLen = BufferUtil::read16(buf);
|
||||
if (clipboardLen > len - 3) {
|
||||
ret = 0; // not available
|
||||
break;
|
||||
}
|
||||
|
||||
QByteArray text = buf.readAll();
|
||||
m_data.clipboardMsg.text = new char[text.length() + 1];
|
||||
memcpy(m_data.clipboardMsg.text, text.data(), text.length());
|
||||
m_data.clipboardMsg.text[text.length()] = '\0';
|
||||
|
||||
ret = 3 + clipboardLen;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning("Unsupported device msg type: %d", (int)m_data.type);
|
||||
ret = -1; // error, we cannot recover
|
||||
}
|
||||
|
||||
buf.close();
|
||||
return ret;
|
||||
}
|
46
QtScrcpy/device/controller/receiver/devicemsg.h
Normal file
46
QtScrcpy/device/controller/receiver/devicemsg.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef DEVICEMSG_H
|
||||
#define DEVICEMSG_H
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#define DEVICE_MSG_QUEUE_SIZE 64
|
||||
#define DEVICE_MSG_TEXT_MAX_LENGTH 4093
|
||||
#define DEVICE_MSG_SERIALIZED_MAX_SIZE (3 + DEVICE_MSG_TEXT_MAX_LENGTH)
|
||||
|
||||
class DeviceMsg : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum DeviceMsgType
|
||||
{
|
||||
DMT_NULL = -1,
|
||||
// 和服务端对应
|
||||
DMT_GET_CLIPBOARD = 0,
|
||||
};
|
||||
explicit DeviceMsg(QObject *parent = nullptr);
|
||||
virtual ~DeviceMsg();
|
||||
|
||||
DeviceMsg::DeviceMsgType type();
|
||||
void getClipboardMsgData(QString &text);
|
||||
|
||||
qint32 deserialize(QByteArray &byteArray);
|
||||
|
||||
private:
|
||||
struct DeviceMsgData
|
||||
{
|
||||
DeviceMsgType type = DMT_NULL;
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
char *text = Q_NULLPTR;
|
||||
} clipboardMsg;
|
||||
};
|
||||
DeviceMsgData() {}
|
||||
~DeviceMsgData() {}
|
||||
};
|
||||
|
||||
DeviceMsgData m_data;
|
||||
};
|
||||
|
||||
#endif // DEVICEMSG_H
|
53
QtScrcpy/device/controller/receiver/receiver.cpp
Normal file
53
QtScrcpy/device/controller/receiver/receiver.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "devicemsg.h"
|
||||
#include "receiver.h"
|
||||
|
||||
Receiver::Receiver(QObject *parent) : QObject(parent) {}
|
||||
|
||||
Receiver::~Receiver() {}
|
||||
|
||||
void Receiver::setControlSocket(QTcpSocket *controlSocket)
|
||||
{
|
||||
if (m_controlSocket || !controlSocket) {
|
||||
return;
|
||||
}
|
||||
m_controlSocket = controlSocket;
|
||||
connect(controlSocket, &QTcpSocket::readyRead, this, &Receiver::onReadyRead);
|
||||
}
|
||||
|
||||
void Receiver::onReadyRead()
|
||||
{
|
||||
if (!m_controlSocket) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (m_controlSocket->bytesAvailable()) {
|
||||
QByteArray byteArray = m_controlSocket->peek(m_controlSocket->bytesAvailable());
|
||||
DeviceMsg deviceMsg;
|
||||
qint32 consume = deviceMsg.deserialize(byteArray);
|
||||
if (0 >= consume) {
|
||||
break;
|
||||
}
|
||||
m_controlSocket->read(consume);
|
||||
processMsg(&deviceMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void Receiver::processMsg(DeviceMsg *deviceMsg)
|
||||
{
|
||||
switch (deviceMsg->type()) {
|
||||
case DeviceMsg::DMT_GET_CLIPBOARD: {
|
||||
qInfo("Device clipboard copied");
|
||||
QClipboard *board = QApplication::clipboard();
|
||||
QString text;
|
||||
deviceMsg->getClipboardMsgData(text);
|
||||
board->setText(text);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
27
QtScrcpy/device/controller/receiver/receiver.h
Normal file
27
QtScrcpy/device/controller/receiver/receiver.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef RECEIVER_H
|
||||
#define RECEIVER_H
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
class QTcpSocket;
|
||||
class DeviceMsg;
|
||||
class Receiver : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Receiver(QObject *parent = Q_NULLPTR);
|
||||
virtual ~Receiver();
|
||||
|
||||
void setControlSocket(QTcpSocket *controlSocket);
|
||||
|
||||
public slots:
|
||||
void onReadyRead();
|
||||
|
||||
protected:
|
||||
void processMsg(DeviceMsg *deviceMsg);
|
||||
|
||||
private:
|
||||
QPointer<QTcpSocket> m_controlSocket;
|
||||
};
|
||||
|
||||
#endif // RECEIVER_H
|
7
QtScrcpy/device/controller/receiver/receiver.pri
Normal file
7
QtScrcpy/device/controller/receiver/receiver.pri
Normal file
|
@ -0,0 +1,7 @@
|
|||
HEADERS += \
|
||||
$$PWD/devicemsg.h \
|
||||
$$PWD/receiver.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/devicemsg.cpp \
|
||||
$$PWD/receiver.cpp
|
74
QtScrcpy/device/decoder/avframeconvert.cpp
Normal file
74
QtScrcpy/device/decoder/avframeconvert.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "avframeconvert.h"
|
||||
|
||||
AVFrameConvert::AVFrameConvert() {}
|
||||
|
||||
AVFrameConvert::~AVFrameConvert() {}
|
||||
|
||||
void AVFrameConvert::setSrcFrameInfo(int srcWidth, int srcHeight, AVPixelFormat srcFormat)
|
||||
{
|
||||
m_srcWidth = srcWidth;
|
||||
m_srcHeight = srcHeight;
|
||||
m_srcFormat = srcFormat;
|
||||
qDebug() << "Convert::src frame info " << srcWidth << "x" << srcHeight;
|
||||
}
|
||||
|
||||
void AVFrameConvert::getSrcFrameInfo(int &srcWidth, int &srcHeight, AVPixelFormat &srcFormat)
|
||||
{
|
||||
srcWidth = m_srcWidth;
|
||||
srcHeight = m_srcHeight;
|
||||
srcFormat = m_srcFormat;
|
||||
}
|
||||
|
||||
void AVFrameConvert::setDstFrameInfo(int dstWidth, int dstHeight, AVPixelFormat dstFormat)
|
||||
{
|
||||
m_dstWidth = dstWidth;
|
||||
m_dstHeight = dstHeight;
|
||||
m_dstFormat = dstFormat;
|
||||
}
|
||||
|
||||
void AVFrameConvert::getDstFrameInfo(int &dstWidth, int &dstHeight, AVPixelFormat &dstFormat)
|
||||
{
|
||||
dstWidth = m_dstWidth;
|
||||
dstHeight = m_dstHeight;
|
||||
dstFormat = m_dstFormat;
|
||||
}
|
||||
|
||||
bool AVFrameConvert::init()
|
||||
{
|
||||
if (m_convertCtx) {
|
||||
return true;
|
||||
}
|
||||
m_convertCtx = sws_getContext(m_srcWidth, m_srcHeight, m_srcFormat, m_dstWidth, m_dstHeight, m_dstFormat, SWS_BICUBIC, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR);
|
||||
if (!m_convertCtx) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AVFrameConvert::isInit()
|
||||
{
|
||||
return m_convertCtx ? true : false;
|
||||
}
|
||||
|
||||
void AVFrameConvert::deInit()
|
||||
{
|
||||
if (m_convertCtx) {
|
||||
sws_freeContext(m_convertCtx);
|
||||
m_convertCtx = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
|
||||
bool AVFrameConvert::convert(const AVFrame *srcFrame, AVFrame *dstFrame)
|
||||
{
|
||||
if (!m_convertCtx || !srcFrame || !dstFrame) {
|
||||
return false;
|
||||
}
|
||||
qint32 ret
|
||||
= sws_scale(m_convertCtx, static_cast<const uint8_t *const *>(srcFrame->data), srcFrame->linesize, 0, m_srcHeight, dstFrame->data, dstFrame->linesize);
|
||||
if (0 == ret) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
40
QtScrcpy/device/decoder/avframeconvert.h
Normal file
40
QtScrcpy/device/decoder/avframeconvert.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
#ifndef AVFRAMECONVERT_H
|
||||
#define AVFRAMECONVERT_H
|
||||
#include <QtGlobal>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavutil/frame.h"
|
||||
#include "libswscale/swscale.h"
|
||||
}
|
||||
|
||||
class AVFrameConvert
|
||||
{
|
||||
public:
|
||||
AVFrameConvert();
|
||||
virtual ~AVFrameConvert();
|
||||
|
||||
public:
|
||||
void setSrcFrameInfo(int srcWidth, int srcHeight, AVPixelFormat srcFormat);
|
||||
void getSrcFrameInfo(int &srcWidth, int &srcHeight, AVPixelFormat &srcFormat);
|
||||
void setDstFrameInfo(int dstWidth, int dstHeight, AVPixelFormat dstFormat);
|
||||
void getDstFrameInfo(int &dstWidth, int &dstHeight, AVPixelFormat &dstFormat);
|
||||
|
||||
bool init();
|
||||
bool isInit();
|
||||
void deInit();
|
||||
bool convert(const AVFrame *srcFrame, AVFrame *dstFrame);
|
||||
|
||||
private:
|
||||
int m_srcWidth = 0;
|
||||
int m_srcHeight = 0;
|
||||
AVPixelFormat m_srcFormat = AV_PIX_FMT_NONE;
|
||||
int m_dstWidth = 0;
|
||||
int m_dstHeight = 0;
|
||||
AVPixelFormat m_dstFormat = AV_PIX_FMT_NONE;
|
||||
|
||||
struct SwsContext *m_convertCtx = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // AVFRAMECONVERT_H
|
120
QtScrcpy/device/decoder/decoder.cpp
Normal file
120
QtScrcpy/device/decoder/decoder.cpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "compat.h"
|
||||
#include "decoder.h"
|
||||
#include "videobuffer.h"
|
||||
|
||||
Decoder::Decoder(VideoBuffer *vb, QObject *parent) : QObject(parent), m_vb(vb) {}
|
||||
|
||||
Decoder::~Decoder() {}
|
||||
|
||||
bool Decoder::open(const AVCodec *codec)
|
||||
{
|
||||
// codec context
|
||||
m_codecCtx = avcodec_alloc_context3(codec);
|
||||
if (!m_codecCtx) {
|
||||
qCritical("Could not allocate decoder context");
|
||||
return false;
|
||||
}
|
||||
if (avcodec_open2(m_codecCtx, codec, NULL) < 0) {
|
||||
qCritical("Could not open H.264 codec");
|
||||
return false;
|
||||
}
|
||||
m_isCodecCtxOpen = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Decoder::close()
|
||||
{
|
||||
if (!m_codecCtx) {
|
||||
return;
|
||||
}
|
||||
if (m_isCodecCtxOpen) {
|
||||
avcodec_close(m_codecCtx);
|
||||
}
|
||||
avcodec_free_context(&m_codecCtx);
|
||||
}
|
||||
|
||||
bool Decoder::push(const AVPacket *packet)
|
||||
{
|
||||
if (!m_codecCtx || !m_vb) {
|
||||
return false;
|
||||
}
|
||||
AVFrame *decodingFrame = m_vb->decodingFrame();
|
||||
#ifdef QTSCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
||||
int ret = -1;
|
||||
if ((ret = avcodec_send_packet(m_codecCtx, packet)) < 0) {
|
||||
char errorbuf[255] = { 0 };
|
||||
av_strerror(ret, errorbuf, 254);
|
||||
qCritical("Could not send video packet: %s", errorbuf);
|
||||
return false;
|
||||
}
|
||||
if (decodingFrame) {
|
||||
ret = avcodec_receive_frame(m_codecCtx, decodingFrame);
|
||||
}
|
||||
if (!ret) {
|
||||
// a frame was received
|
||||
pushFrame();
|
||||
|
||||
//emit getOneFrame(yuvDecoderFrame->data[0], yuvDecoderFrame->data[1], yuvDecoderFrame->data[2],
|
||||
// yuvDecoderFrame->linesize[0], yuvDecoderFrame->linesize[1], yuvDecoderFrame->linesize[2]);
|
||||
|
||||
/*
|
||||
// m_conver转换yuv为rgb是使用cpu转的,占用cpu太高,改用opengl渲染yuv
|
||||
// QImage的copy也非常占用内存,此方案不考虑
|
||||
if (!m_conver.isInit()) {
|
||||
qDebug() << "decoder frame format" << decodingFrame->format;
|
||||
m_conver.setSrcFrameInfo(codecCtx->width, codecCtx->height, AV_PIX_FMT_YUV420P);
|
||||
m_conver.setDstFrameInfo(codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32);
|
||||
m_conver.init();
|
||||
}
|
||||
if (!outBuffer) {
|
||||
outBuffer=new quint8[avpicture_get_size(AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height)];
|
||||
avpicture_fill((AVPicture *)rgbDecoderFrame, outBuffer, AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height);
|
||||
}
|
||||
m_conver.convert(decodingFrame, rgbDecoderFrame);
|
||||
//QImage tmpImg((uchar *)outBuffer, codecCtx->width, codecCtx->height, QImage::Format_RGB32);
|
||||
//QImage image = tmpImg.copy();
|
||||
//emit getOneImage(image);
|
||||
*/
|
||||
} else if (ret != AVERROR(EAGAIN)) {
|
||||
qCritical("Could not receive video frame: %d", ret);
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
int gotPicture = 0;
|
||||
int len = -1;
|
||||
if (decodingFrame) {
|
||||
len = avcodec_decode_video2(m_codecCtx, decodingFrame, &gotPicture, packet);
|
||||
}
|
||||
if (len < 0) {
|
||||
qCritical("Could not decode video packet: %d", len);
|
||||
return false;
|
||||
}
|
||||
if (gotPicture) {
|
||||
pushFrame();
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void Decoder::interrupt()
|
||||
{
|
||||
if (m_vb) {
|
||||
m_vb->interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
void Decoder::pushFrame()
|
||||
{
|
||||
if (!m_vb) {
|
||||
return;
|
||||
}
|
||||
bool previousFrameSkipped = true;
|
||||
m_vb->offerDecodedFrame(previousFrameSkipped);
|
||||
if (previousFrameSkipped) {
|
||||
// the previous newFrame will consume this frame
|
||||
return;
|
||||
}
|
||||
emit onNewFrame();
|
||||
}
|
35
QtScrcpy/device/decoder/decoder.h
Normal file
35
QtScrcpy/device/decoder/decoder.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
#include <QObject>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
}
|
||||
|
||||
class VideoBuffer;
|
||||
class Decoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Decoder(VideoBuffer *vb, QObject *parent = Q_NULLPTR);
|
||||
virtual ~Decoder();
|
||||
|
||||
bool open(const AVCodec *codec);
|
||||
void close();
|
||||
bool push(const AVPacket *packet);
|
||||
void interrupt();
|
||||
|
||||
signals:
|
||||
void onNewFrame();
|
||||
|
||||
protected:
|
||||
void pushFrame();
|
||||
|
||||
private:
|
||||
VideoBuffer *m_vb = Q_NULLPTR;
|
||||
AVCodecContext *m_codecCtx = Q_NULLPTR;
|
||||
bool m_isCodecCtxOpen = false;
|
||||
};
|
||||
|
||||
#endif // DECODER_H
|
11
QtScrcpy/device/decoder/decoder.pri
Normal file
11
QtScrcpy/device/decoder/decoder.pri
Normal file
|
@ -0,0 +1,11 @@
|
|||
HEADERS += \
|
||||
$$PWD/decoder.h \
|
||||
$$PWD/fpscounter.h \
|
||||
$$PWD/avframeconvert.h \
|
||||
$$PWD/videobuffer.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/decoder.cpp \
|
||||
$$PWD/fpscounter.cpp \
|
||||
$$PWD/avframeconvert.cpp \
|
||||
$$PWD/videobuffer.cpp
|
66
QtScrcpy/device/decoder/fpscounter.cpp
Normal file
66
QtScrcpy/device/decoder/fpscounter.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include <QDebug>
|
||||
#include <QTimerEvent>
|
||||
|
||||
#include "fpscounter.h"
|
||||
|
||||
FpsCounter::FpsCounter(QObject *parent) : QObject(parent) {}
|
||||
|
||||
FpsCounter::~FpsCounter() {}
|
||||
|
||||
void FpsCounter::start()
|
||||
{
|
||||
resetCounter();
|
||||
startCounterTimer();
|
||||
}
|
||||
|
||||
void FpsCounter::stop()
|
||||
{
|
||||
stopCounterTimer();
|
||||
resetCounter();
|
||||
}
|
||||
|
||||
bool FpsCounter::isStarted()
|
||||
{
|
||||
return m_counterTimer;
|
||||
}
|
||||
|
||||
void FpsCounter::addRenderedFrame()
|
||||
{
|
||||
m_rendered++;
|
||||
}
|
||||
|
||||
void FpsCounter::addSkippedFrame()
|
||||
{
|
||||
m_skipped++;
|
||||
}
|
||||
|
||||
void FpsCounter::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
if (event && m_counterTimer == event->timerId()) {
|
||||
m_curRendered = m_rendered;
|
||||
m_curSkipped = m_skipped;
|
||||
resetCounter();
|
||||
emit updateFPS(m_curRendered);
|
||||
//qInfo("FPS:%d Discard:%d", m_curRendered, m_skipped);
|
||||
}
|
||||
}
|
||||
|
||||
void FpsCounter::startCounterTimer()
|
||||
{
|
||||
stopCounterTimer();
|
||||
m_counterTimer = startTimer(1000);
|
||||
}
|
||||
|
||||
void FpsCounter::stopCounterTimer()
|
||||
{
|
||||
if (m_counterTimer) {
|
||||
killTimer(m_counterTimer);
|
||||
m_counterTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void FpsCounter::resetCounter()
|
||||
{
|
||||
m_rendered = 0;
|
||||
m_skipped = 0;
|
||||
}
|
38
QtScrcpy/device/decoder/fpscounter.h
Normal file
38
QtScrcpy/device/decoder/fpscounter.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef FPSCOUNTER_H
|
||||
#define FPSCOUNTER_H
|
||||
#include <QObject>
|
||||
|
||||
class FpsCounter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FpsCounter(QObject *parent = Q_NULLPTR);
|
||||
virtual ~FpsCounter();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool isStarted();
|
||||
void addRenderedFrame();
|
||||
void addSkippedFrame();
|
||||
|
||||
signals:
|
||||
void updateFPS(quint32 fps);
|
||||
|
||||
protected:
|
||||
virtual void timerEvent(QTimerEvent *event);
|
||||
|
||||
private:
|
||||
void startCounterTimer();
|
||||
void stopCounterTimer();
|
||||
void resetCounter();
|
||||
|
||||
private:
|
||||
qint32 m_counterTimer = 0;
|
||||
quint32 m_curRendered = 0;
|
||||
quint32 m_curSkipped = 0;
|
||||
|
||||
quint32 m_rendered = 0;
|
||||
quint32 m_skipped = 0;
|
||||
};
|
||||
|
||||
#endif // FPSCOUNTER_H
|
128
QtScrcpy/device/decoder/videobuffer.cpp
Normal file
128
QtScrcpy/device/decoder/videobuffer.cpp
Normal file
|
@ -0,0 +1,128 @@
|
|||
#include "videobuffer.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
}
|
||||
|
||||
VideoBuffer::VideoBuffer() {}
|
||||
|
||||
VideoBuffer::~VideoBuffer() {}
|
||||
|
||||
bool VideoBuffer::init(bool renderExpiredFrames)
|
||||
{
|
||||
m_renderExpiredFrames = renderExpiredFrames;
|
||||
m_decodingFrame = av_frame_alloc();
|
||||
if (!m_decodingFrame) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
m_renderingframe = av_frame_alloc();
|
||||
if (!m_renderingframe) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
// there is initially no rendering frame, so consider it has already been
|
||||
// consumed
|
||||
m_renderingFrameConsumed = true;
|
||||
|
||||
m_fpsCounter.start();
|
||||
return true;
|
||||
|
||||
error:
|
||||
deInit();
|
||||
return false;
|
||||
}
|
||||
|
||||
void VideoBuffer::deInit()
|
||||
{
|
||||
if (m_decodingFrame) {
|
||||
av_frame_free(&m_decodingFrame);
|
||||
m_decodingFrame = Q_NULLPTR;
|
||||
}
|
||||
if (m_renderingframe) {
|
||||
av_frame_free(&m_renderingframe);
|
||||
m_renderingframe = Q_NULLPTR;
|
||||
}
|
||||
m_fpsCounter.stop();
|
||||
}
|
||||
|
||||
void VideoBuffer::lock()
|
||||
{
|
||||
m_mutex.lock();
|
||||
}
|
||||
|
||||
void VideoBuffer::unLock()
|
||||
{
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
AVFrame *VideoBuffer::decodingFrame()
|
||||
{
|
||||
return m_decodingFrame;
|
||||
}
|
||||
|
||||
void VideoBuffer::offerDecodedFrame(bool &previousFrameSkipped)
|
||||
{
|
||||
m_mutex.lock();
|
||||
|
||||
if (m_renderExpiredFrames) {
|
||||
// if m_renderExpiredFrames is enable, then the decoder must wait for the current
|
||||
// frame to be consumed
|
||||
while (!m_renderingFrameConsumed && !m_interrupted) {
|
||||
m_renderingFrameConsumedCond.wait(&m_mutex);
|
||||
}
|
||||
} else {
|
||||
if (m_fpsCounter.isStarted() && !m_renderingFrameConsumed) {
|
||||
m_fpsCounter.addSkippedFrame();
|
||||
}
|
||||
}
|
||||
|
||||
swap();
|
||||
previousFrameSkipped = !m_renderingFrameConsumed;
|
||||
m_renderingFrameConsumed = false;
|
||||
m_mutex.unlock();
|
||||
}
|
||||
|
||||
const AVFrame *VideoBuffer::consumeRenderedFrame()
|
||||
{
|
||||
Q_ASSERT(!m_renderingFrameConsumed);
|
||||
m_renderingFrameConsumed = true;
|
||||
if (m_fpsCounter.isStarted()) {
|
||||
m_fpsCounter.addRenderedFrame();
|
||||
}
|
||||
if (m_renderExpiredFrames) {
|
||||
// if m_renderExpiredFrames is enable, then notify the decoder the current frame is
|
||||
// consumed, so that it may push a new one
|
||||
m_renderingFrameConsumedCond.wakeOne();
|
||||
}
|
||||
return m_renderingframe;
|
||||
}
|
||||
|
||||
const AVFrame *VideoBuffer::peekRenderedFrame()
|
||||
{
|
||||
return m_renderingframe;
|
||||
}
|
||||
|
||||
void VideoBuffer::interrupt()
|
||||
{
|
||||
if (m_renderExpiredFrames) {
|
||||
m_mutex.lock();
|
||||
m_interrupted = true;
|
||||
m_mutex.unlock();
|
||||
// wake up blocking wait
|
||||
m_renderingFrameConsumedCond.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
FpsCounter *VideoBuffer::getFPSCounter()
|
||||
{
|
||||
return &m_fpsCounter;
|
||||
}
|
||||
|
||||
void VideoBuffer::swap()
|
||||
{
|
||||
AVFrame *tmp = m_decodingFrame;
|
||||
m_decodingFrame = m_renderingframe;
|
||||
m_renderingframe = tmp;
|
||||
}
|
60
QtScrcpy/device/decoder/videobuffer.h
Normal file
60
QtScrcpy/device/decoder/videobuffer.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
#ifndef VIDEO_BUFFER_H
|
||||
#define VIDEO_BUFFER_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
|
||||
#include "fpscounter.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
class VideoBuffer
|
||||
{
|
||||
public:
|
||||
VideoBuffer();
|
||||
virtual ~VideoBuffer();
|
||||
|
||||
bool init(bool renderExpiredFrames = false);
|
||||
void deInit();
|
||||
void lock();
|
||||
void unLock();
|
||||
|
||||
AVFrame *decodingFrame();
|
||||
// set the decoder frame as ready for rendering
|
||||
// this function locks m_mutex during its execution
|
||||
// returns true if the previous frame had been consumed
|
||||
void offerDecodedFrame(bool &previousFrameSkipped);
|
||||
|
||||
// mark the rendering frame as consumed and return it
|
||||
// MUST be called with m_mutex locked!!!
|
||||
// the caller is expected to render the returned frame to some texture before
|
||||
// unlocking m_mutex
|
||||
const AVFrame *consumeRenderedFrame();
|
||||
|
||||
const AVFrame *peekRenderedFrame();
|
||||
|
||||
// wake up and avoid any blocking call
|
||||
void interrupt();
|
||||
|
||||
FpsCounter *getFPSCounter();
|
||||
|
||||
private:
|
||||
void swap();
|
||||
|
||||
private:
|
||||
AVFrame *m_decodingFrame = Q_NULLPTR;
|
||||
AVFrame *m_renderingframe = Q_NULLPTR;
|
||||
QMutex m_mutex;
|
||||
bool m_renderingFrameConsumed = true;
|
||||
FpsCounter m_fpsCounter;
|
||||
|
||||
bool m_renderExpiredFrames = false;
|
||||
QWaitCondition m_renderingFrameConsumedCond;
|
||||
|
||||
// interrupted is not used if expired frames are not rendered
|
||||
// since offering a frame will never block
|
||||
bool m_interrupted = false;
|
||||
};
|
||||
|
||||
#endif // VIDEO_BUFFER_H
|
393
QtScrcpy/device/device.cpp
Normal file
393
QtScrcpy/device/device.cpp
Normal file
|
@ -0,0 +1,393 @@
|
|||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
|
||||
#include "avframeconvert.h"
|
||||
#include "config.h"
|
||||
#include "controller.h"
|
||||
#include "decoder.h"
|
||||
#include "device.h"
|
||||
#include "filehandler.h"
|
||||
#include "mousetap/mousetap.h"
|
||||
#include "recorder.h"
|
||||
#include "server.h"
|
||||
#include "stream.h"
|
||||
#include "videobuffer.h"
|
||||
#include "videoform.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavutil/imgutils.h"
|
||||
}
|
||||
|
||||
Device::Device(DeviceParams params, QObject *parent) : QObject(parent), m_params(params)
|
||||
{
|
||||
if (!params.display && m_params.recordFileName.trimmed().isEmpty()) {
|
||||
qCritical("not display must be recorded");
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.display) {
|
||||
m_vb = new VideoBuffer();
|
||||
m_vb->init(params.renderExpiredFrames);
|
||||
m_decoder = new Decoder(m_vb, this);
|
||||
m_fileHandler = new FileHandler(this);
|
||||
m_controller = new Controller(params.gameScript, this);
|
||||
m_videoForm = new VideoForm(Config::getInstance().getFramelessWindow(), Config::getInstance().getSkin());
|
||||
m_videoForm->setDevice(this);
|
||||
}
|
||||
|
||||
m_stream = new Stream(this);
|
||||
if (m_decoder) {
|
||||
m_stream->setDecoder(m_decoder);
|
||||
}
|
||||
m_server = new Server(this);
|
||||
if (!m_params.recordFileName.trimmed().isEmpty()) {
|
||||
m_recorder = new Recorder(m_params.recordFileName);
|
||||
m_stream->setRecoder(m_recorder);
|
||||
}
|
||||
initSignals();
|
||||
startServer();
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
if (m_server) {
|
||||
m_server->stop();
|
||||
}
|
||||
// server must stop before decoder, because decoder block main thread
|
||||
if (m_stream) {
|
||||
m_stream->stopDecode();
|
||||
}
|
||||
|
||||
if (m_recorder) {
|
||||
delete m_recorder;
|
||||
}
|
||||
if (m_vb) {
|
||||
m_vb->deInit();
|
||||
delete m_vb;
|
||||
}
|
||||
if (m_videoForm) {
|
||||
m_videoForm->close();
|
||||
delete m_videoForm;
|
||||
}
|
||||
emit deviceDisconnect(m_params.serial);
|
||||
}
|
||||
|
||||
VideoForm *Device::getVideoForm()
|
||||
{
|
||||
return m_videoForm;
|
||||
}
|
||||
|
||||
Server *Device::getServer()
|
||||
{
|
||||
return m_server;
|
||||
}
|
||||
|
||||
const QString &Device::getSerial()
|
||||
{
|
||||
return m_params.serial;
|
||||
}
|
||||
|
||||
const QSize Device::frameSize()
|
||||
{
|
||||
QSize size;
|
||||
if (!m_videoForm) {
|
||||
return size;
|
||||
}
|
||||
return m_videoForm->frameSize();
|
||||
}
|
||||
|
||||
void Device::updateScript(QString script)
|
||||
{
|
||||
if (m_controller) {
|
||||
m_controller->updateScript(script);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::onScreenshot()
|
||||
{
|
||||
if (!m_vb) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_vb->lock();
|
||||
// screenshot
|
||||
saveFrame(m_vb->peekRenderedFrame());
|
||||
m_vb->unLock();
|
||||
}
|
||||
|
||||
void Device::onShowTouch(bool show)
|
||||
{
|
||||
AdbProcess *adb = new AdbProcess();
|
||||
if (!adb) {
|
||||
return;
|
||||
}
|
||||
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
sender()->deleteLater();
|
||||
}
|
||||
});
|
||||
adb->setShowTouchesEnabled(getSerial(), show);
|
||||
|
||||
qInfo() << getSerial() << " show touch " << (show ? "enable" : "disable");
|
||||
}
|
||||
|
||||
void Device::initSignals()
|
||||
{
|
||||
connect(this, &Device::screenshot, this, &Device::onScreenshot);
|
||||
connect(this, &Device::showTouch, this, &Device::onShowTouch);
|
||||
connect(this, &Device::setControlState, this, &Device::onSetControlState);
|
||||
connect(this, &Device::grabCursor, this, &Device::onGrabCursor);
|
||||
|
||||
if (m_controller) {
|
||||
connect(m_controller, &Controller::grabCursor, this, &Device::grabCursor);
|
||||
}
|
||||
if (m_controller) {
|
||||
connect(this, &Device::postGoBack, m_controller, &Controller::onPostGoBack);
|
||||
connect(this, &Device::postGoHome, m_controller, &Controller::onPostGoHome);
|
||||
connect(this, &Device::postGoMenu, m_controller, &Controller::onPostGoMenu);
|
||||
connect(this, &Device::postAppSwitch, m_controller, &Controller::onPostAppSwitch);
|
||||
connect(this, &Device::postPower, m_controller, &Controller::onPostPower);
|
||||
connect(this, &Device::postVolumeUp, m_controller, &Controller::onPostVolumeUp);
|
||||
connect(this, &Device::postVolumeDown, m_controller, &Controller::onPostVolumeDown);
|
||||
connect(this, &Device::setScreenPowerMode, m_controller, &Controller::onSetScreenPowerMode);
|
||||
connect(this, &Device::expandNotificationPanel, m_controller, &Controller::onExpandNotificationPanel);
|
||||
connect(this, &Device::collapseNotificationPanel, m_controller, &Controller::onCollapseNotificationPanel);
|
||||
connect(this, &Device::mouseEvent, m_controller, &Controller::onMouseEvent);
|
||||
connect(this, &Device::wheelEvent, m_controller, &Controller::onWheelEvent);
|
||||
connect(this, &Device::keyEvent, m_controller, &Controller::onKeyEvent);
|
||||
|
||||
connect(this, &Device::postBackOrScreenOn, m_controller, &Controller::onPostBackOrScreenOn);
|
||||
connect(this, &Device::requestDeviceClipboard, m_controller, &Controller::onRequestDeviceClipboard);
|
||||
connect(this, &Device::setDeviceClipboard, m_controller, &Controller::onSetDeviceClipboard);
|
||||
connect(this, &Device::clipboardPaste, m_controller, &Controller::onClipboardPaste);
|
||||
connect(this, &Device::postTextInput, m_controller, &Controller::onPostTextInput);
|
||||
}
|
||||
if (m_videoForm) {
|
||||
connect(m_videoForm, &VideoForm::destroyed, this, [this](QObject *obj) {
|
||||
Q_UNUSED(obj)
|
||||
deleteLater();
|
||||
});
|
||||
|
||||
connect(this, &Device::switchFullScreen, m_videoForm, &VideoForm::onSwitchFullScreen);
|
||||
}
|
||||
if (m_fileHandler) {
|
||||
connect(this, &Device::pushFileRequest, this, [this](const QString &file, const QString &devicePath) {
|
||||
m_fileHandler->onPushFileRequest(getSerial(), file, devicePath);
|
||||
});
|
||||
connect(this, &Device::installApkRequest, this, [this](const QString &apkFile) { m_fileHandler->onInstallApkRequest(getSerial(), apkFile); });
|
||||
connect(m_fileHandler, &FileHandler::fileHandlerResult, this, [this](FileHandler::FILE_HANDLER_RESULT processResult, bool isApk) {
|
||||
QString tipsType = "";
|
||||
if (isApk) {
|
||||
tipsType = tr("install apk");
|
||||
} else {
|
||||
tipsType = tr("file transfer");
|
||||
}
|
||||
QString tips;
|
||||
if (FileHandler::FAR_IS_RUNNING == processResult && m_videoForm) {
|
||||
tips = tr("wait current %1 to complete").arg(tipsType);
|
||||
}
|
||||
if (FileHandler::FAR_SUCCESS_EXEC == processResult && m_videoForm) {
|
||||
tips = tr("%1 complete, save in %2").arg(tipsType).arg(Config::getInstance().getPushFilePath());
|
||||
}
|
||||
if (FileHandler::FAR_ERROR_EXEC == processResult && m_videoForm) {
|
||||
tips = tr("%1 failed").arg(tipsType);
|
||||
}
|
||||
qInfo() << tips;
|
||||
if (m_controlState == GCS_CLIENT) {
|
||||
return;
|
||||
}
|
||||
QMessageBox::information(m_videoForm, "QtScrcpy", tips, QMessageBox::Ok);
|
||||
});
|
||||
}
|
||||
|
||||
if (m_server) {
|
||||
connect(m_server, &Server::serverStartResult, this, [this](bool success) {
|
||||
if (success) {
|
||||
m_server->connectTo();
|
||||
} else {
|
||||
deleteLater();
|
||||
}
|
||||
});
|
||||
connect(m_server, &Server::connectToResult, this, [this](bool success, const QString &deviceName, const QSize &size) {
|
||||
if (success) {
|
||||
double diff = m_startTimeCount.elapsed() / 1000.0;
|
||||
qInfo() << QString("server start finish in %1s").arg(diff).toStdString().c_str();
|
||||
|
||||
// update ui
|
||||
if (m_videoForm) {
|
||||
// must be show before updateShowSize
|
||||
m_videoForm->show();
|
||||
|
||||
m_videoForm->setWindowTitle(deviceName);
|
||||
m_videoForm->updateShowSize(size);
|
||||
|
||||
bool deviceVer = size.height() > size.width();
|
||||
QRect rc = Config::getInstance().getRect(getSerial());
|
||||
bool rcVer = rc.height() > rc.width();
|
||||
// same width/height rate
|
||||
if (rc.isValid() && (deviceVer == rcVer)) {
|
||||
// mark: resize is for fix setGeometry magneticwidget bug
|
||||
m_videoForm->resize(rc.size());
|
||||
m_videoForm->setGeometry(rc);
|
||||
}
|
||||
}
|
||||
|
||||
// init recorder
|
||||
if (m_recorder) {
|
||||
m_recorder->setFrameSize(size);
|
||||
}
|
||||
|
||||
// init decoder
|
||||
m_stream->setVideoSocket(m_server->getVideoSocket());
|
||||
m_stream->startDecode();
|
||||
|
||||
// init controller
|
||||
if (m_controller) {
|
||||
m_controller->setControlSocket(m_server->getControlSocket());
|
||||
}
|
||||
|
||||
// 显示界面时才自动息屏(m_params.display)
|
||||
if (m_params.closeScreen && m_params.display && m_controller) {
|
||||
emit m_controller->onSetScreenPowerMode(ControlMsg::SPM_OFF);
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_server, &Server::onServerStop, this, [this]() {
|
||||
deleteLater();
|
||||
qDebug() << "server process stop";
|
||||
});
|
||||
}
|
||||
|
||||
if (m_stream) {
|
||||
connect(m_stream, &Stream::onStreamStop, this, [this]() {
|
||||
deleteLater();
|
||||
qDebug() << "stream thread stop";
|
||||
});
|
||||
}
|
||||
|
||||
if (m_decoder && m_vb) {
|
||||
// must be Qt::QueuedConnection, ui update must be main thread
|
||||
connect(
|
||||
m_decoder,
|
||||
&Decoder::onNewFrame,
|
||||
this,
|
||||
[this]() {
|
||||
m_vb->lock();
|
||||
const AVFrame *frame = m_vb->consumeRenderedFrame();
|
||||
if (m_videoForm) {
|
||||
m_videoForm->updateRender(frame);
|
||||
}
|
||||
m_vb->unLock();
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(m_vb->getFPSCounter(), &::FpsCounter::updateFPS, m_videoForm, &VideoForm::updateFPS);
|
||||
}
|
||||
}
|
||||
|
||||
void Device::startServer()
|
||||
{
|
||||
// fix: macos cant recv finished signel, timer is ok
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
m_startTimeCount.start();
|
||||
// max size support 480p 720p 1080p 设备原生分辨率
|
||||
// support wireless connect, example:
|
||||
//m_server->start("192.168.0.174:5555", 27183, m_maxSize, m_bitRate, "");
|
||||
// only one devices, serial can be null
|
||||
// mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0"
|
||||
Server::ServerParams params;
|
||||
params.serial = m_params.serial;
|
||||
params.localPort = m_params.localPort;
|
||||
params.maxSize = m_params.maxSize;
|
||||
params.bitRate = m_params.bitRate;
|
||||
params.maxFps = m_params.maxFps;
|
||||
params.crop = "-";
|
||||
params.control = true;
|
||||
params.useReverse = m_params.useReverse;
|
||||
params.lockVideoOrientation = m_params.lockVideoOrientation;
|
||||
params.stayAwake = m_params.stayAwake;
|
||||
m_server->start(params);
|
||||
});
|
||||
}
|
||||
|
||||
void Device::onSetControlState(Device *device, Device::GroupControlState state)
|
||||
{
|
||||
Q_UNUSED(device)
|
||||
if (m_controlState == state) {
|
||||
return;
|
||||
}
|
||||
GroupControlState oldState = m_controlState;
|
||||
m_controlState = state;
|
||||
emit controlStateChange(this, oldState, m_controlState);
|
||||
}
|
||||
|
||||
void Device::onGrabCursor(bool grab)
|
||||
{
|
||||
if (!m_videoForm) {
|
||||
return;
|
||||
}
|
||||
if (m_controlState == GCS_CLIENT) {
|
||||
return;
|
||||
}
|
||||
QRect rc = m_videoForm->getGrabCursorRect();
|
||||
MouseTap::getInstance()->enableMouseEventTap(rc, grab);
|
||||
}
|
||||
|
||||
Device::GroupControlState Device::controlState()
|
||||
{
|
||||
return m_controlState;
|
||||
}
|
||||
|
||||
bool Device::saveFrame(const AVFrame *frame)
|
||||
{
|
||||
if (!frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// create buffer
|
||||
QImage rgbImage(frame->width, frame->height, QImage::Format_RGB32);
|
||||
AVFrame *rgbFrame = av_frame_alloc();
|
||||
if (!rgbFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// bind buffer to AVFrame
|
||||
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbImage.bits(), AV_PIX_FMT_RGB32, frame->width, frame->height, 4);
|
||||
|
||||
// convert
|
||||
AVFrameConvert convert;
|
||||
convert.setSrcFrameInfo(frame->width, frame->height, AV_PIX_FMT_YUV420P);
|
||||
convert.setDstFrameInfo(frame->width, frame->height, AV_PIX_FMT_RGB32);
|
||||
bool ret = false;
|
||||
ret = convert.init();
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
ret = convert.convert(frame, rgbFrame);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
convert.deInit();
|
||||
av_free(rgbFrame);
|
||||
|
||||
// save
|
||||
QString absFilePath;
|
||||
QString fileDir(Config::getInstance().getRecordPath());
|
||||
if (fileDir.isEmpty()) {
|
||||
qWarning() << "please select record save path!!!";
|
||||
return false;
|
||||
}
|
||||
QDateTime dateTime = QDateTime::currentDateTime();
|
||||
QString fileName = dateTime.toString("_yyyyMMdd_hhmmss_zzz");
|
||||
fileName = Config::getInstance().getTitle() + fileName + ".png";
|
||||
QDir dir(fileDir);
|
||||
absFilePath = dir.absoluteFilePath(fileName);
|
||||
ret = rgbImage.save(absFilePath, "PNG", 100);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qInfo() << "screenshot save to " << absFilePath;
|
||||
return true;
|
||||
}
|
125
QtScrcpy/device/device.h
Normal file
125
QtScrcpy/device/device.h
Normal file
|
@ -0,0 +1,125 @@
|
|||
#ifndef DEVICE_H
|
||||
#define DEVICE_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QTime>
|
||||
|
||||
#include "controlmsg.h"
|
||||
|
||||
class QMouseEvent;
|
||||
class QWheelEvent;
|
||||
class QKeyEvent;
|
||||
class Recorder;
|
||||
class Server;
|
||||
class VideoBuffer;
|
||||
class Decoder;
|
||||
class FileHandler;
|
||||
class Stream;
|
||||
class VideoForm;
|
||||
class Controller;
|
||||
struct AVFrame;
|
||||
class Device : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
struct DeviceParams
|
||||
{
|
||||
QString recordFileName = ""; // 视频录制文件名
|
||||
QString serial = ""; // 设备序列号
|
||||
quint16 localPort = 27183; // reverse时本地监听端口
|
||||
quint16 maxSize = 720; // 视频分辨率
|
||||
quint32 bitRate = 8000000; // 视频比特率
|
||||
quint32 maxFps = 60; // 视频最大帧率
|
||||
bool closeScreen = false; // 启动时自动息屏
|
||||
bool useReverse = true; // true:先使用adb reverse,失败后自动使用adb forward;false:直接使用adb forward
|
||||
bool display = true; // 是否显示画面(或者仅仅后台录制)
|
||||
QString gameScript = ""; // 游戏映射脚本
|
||||
bool renderExpiredFrames = false; // 是否渲染延迟视频帧
|
||||
int lockVideoOrientation = -1; // 是否锁定视频方向
|
||||
int stayAwake = false; // 是否保持唤醒
|
||||
};
|
||||
enum GroupControlState
|
||||
{
|
||||
GCS_FREE = 0,
|
||||
GCS_HOST,
|
||||
GCS_CLIENT,
|
||||
};
|
||||
explicit Device(DeviceParams params, QObject *parent = nullptr);
|
||||
virtual ~Device();
|
||||
|
||||
VideoForm *getVideoForm();
|
||||
Server *getServer();
|
||||
const QString &getSerial();
|
||||
const QSize frameSize();
|
||||
|
||||
void updateScript(QString script);
|
||||
Device::GroupControlState controlState();
|
||||
|
||||
signals:
|
||||
void deviceDisconnect(QString serial);
|
||||
|
||||
// tool bar
|
||||
void switchFullScreen();
|
||||
void postGoBack();
|
||||
void postGoHome();
|
||||
void postGoMenu();
|
||||
void postAppSwitch();
|
||||
void postPower();
|
||||
void postVolumeUp();
|
||||
void postVolumeDown();
|
||||
void setScreenPowerMode(ControlMsg::ScreenPowerMode mode);
|
||||
void expandNotificationPanel();
|
||||
void collapseNotificationPanel();
|
||||
void postBackOrScreenOn();
|
||||
void postTextInput(QString &text);
|
||||
void requestDeviceClipboard();
|
||||
void setDeviceClipboard();
|
||||
void clipboardPaste();
|
||||
void pushFileRequest(const QString &file, const QString &devicePath = "");
|
||||
void installApkRequest(const QString &apkFile);
|
||||
|
||||
// key map
|
||||
void mouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void keyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
|
||||
// self connect signal and slots
|
||||
void screenshot();
|
||||
void showTouch(bool show);
|
||||
void setControlState(Device *device, Device::GroupControlState state);
|
||||
void grabCursor(bool grab);
|
||||
|
||||
// for notify
|
||||
void controlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
|
||||
|
||||
public slots:
|
||||
void onScreenshot();
|
||||
void onShowTouch(bool show);
|
||||
void onSetControlState(Device *device, Device::GroupControlState state);
|
||||
void onGrabCursor(bool grab);
|
||||
|
||||
private:
|
||||
void initSignals();
|
||||
void startServer();
|
||||
bool saveFrame(const AVFrame *frame);
|
||||
|
||||
private:
|
||||
// server relevant
|
||||
QPointer<Server> m_server;
|
||||
QPointer<Decoder> m_decoder;
|
||||
QPointer<Controller> m_controller;
|
||||
QPointer<FileHandler> m_fileHandler;
|
||||
QPointer<Stream> m_stream;
|
||||
VideoBuffer *m_vb = Q_NULLPTR;
|
||||
Recorder *m_recorder = Q_NULLPTR;
|
||||
|
||||
// ui
|
||||
QPointer<VideoForm> m_videoForm;
|
||||
|
||||
QTime m_startTimeCount;
|
||||
DeviceParams m_params;
|
||||
|
||||
GroupControlState m_controlState = GCS_FREE;
|
||||
};
|
||||
|
||||
#endif // DEVICE_H
|
27
QtScrcpy/device/device.pri
Normal file
27
QtScrcpy/device/device.pri
Normal file
|
@ -0,0 +1,27 @@
|
|||
HEADERS += \
|
||||
$$PWD/device.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/device.cpp
|
||||
|
||||
include ($$PWD/server/server.pri)
|
||||
include ($$PWD/decoder/decoder.pri)
|
||||
include ($$PWD/render/render.pri)
|
||||
include ($$PWD/stream/stream.pri)
|
||||
include ($$PWD/android/android.pri)
|
||||
include ($$PWD/controller/controller.pri)
|
||||
include ($$PWD/filehandler/filehandler.pri)
|
||||
include ($$PWD/recorder/recorder.pri)
|
||||
include ($$PWD/ui/ui.pri)
|
||||
|
||||
INCLUDEPATH += \
|
||||
$$PWD/../../third_party/ffmpeg/include \
|
||||
$$PWD/server \
|
||||
$$PWD/decoder \
|
||||
$$PWD/render \
|
||||
$$PWD/stream \
|
||||
$$PWD/android \
|
||||
$$PWD/controller \
|
||||
$$PWD/filehandler \
|
||||
$$PWD/recorder \
|
||||
$$PWD/ui
|
42
QtScrcpy/device/filehandler/filehandler.cpp
Normal file
42
QtScrcpy/device/filehandler/filehandler.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "filehandler.h"
|
||||
|
||||
FileHandler::FileHandler(QObject *parent) : QObject(parent)
|
||||
{
|
||||
connect(&m_adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
switch (processResult) {
|
||||
case AdbProcess::AER_ERROR_START:
|
||||
case AdbProcess::AER_ERROR_EXEC:
|
||||
case AdbProcess::AER_ERROR_MISSING_BINARY:
|
||||
emit fileHandlerResult(FAR_ERROR_EXEC, m_isApk);
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_EXEC:
|
||||
emit fileHandlerResult(FAR_SUCCESS_EXEC, m_isApk);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
FileHandler::~FileHandler() {}
|
||||
|
||||
void FileHandler::onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath)
|
||||
{
|
||||
if (m_adb.isRuning()) {
|
||||
emit fileHandlerResult(FAR_IS_RUNNING, false);
|
||||
return;
|
||||
}
|
||||
|
||||
m_isApk = false;
|
||||
m_adb.push(serial, file, devicePath);
|
||||
}
|
||||
|
||||
void FileHandler::onInstallApkRequest(const QString &serial, const QString &apkFile)
|
||||
{
|
||||
if (m_adb.isRuning()) {
|
||||
emit fileHandlerResult(FAR_IS_RUNNING, true);
|
||||
return;
|
||||
}
|
||||
m_isApk = true;
|
||||
m_adb.install(serial, apkFile);
|
||||
}
|
36
QtScrcpy/device/filehandler/filehandler.h
Normal file
36
QtScrcpy/device/filehandler/filehandler.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef FILEHANDLER_H
|
||||
#define FILEHANDLER_H
|
||||
#include <QObject>
|
||||
|
||||
#include "adbprocess.h"
|
||||
|
||||
class FileHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum FILE_HANDLER_RESULT
|
||||
{
|
||||
FAR_IS_RUNNING, // 正在执行
|
||||
FAR_SUCCESS_EXEC, // 执行成功
|
||||
FAR_ERROR_EXEC, // 执行失败
|
||||
};
|
||||
|
||||
FileHandler(QObject *parent = nullptr);
|
||||
virtual ~FileHandler();
|
||||
|
||||
const QString &getDevicePath();
|
||||
|
||||
public slots:
|
||||
void onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath = "");
|
||||
void onInstallApkRequest(const QString &serial, const QString &apkFile);
|
||||
|
||||
signals:
|
||||
void fileHandlerResult(FILE_HANDLER_RESULT processResult, bool isApk = false);
|
||||
|
||||
private:
|
||||
AdbProcess m_adb;
|
||||
bool m_isApk = false;
|
||||
QString m_devicePath = "";
|
||||
};
|
||||
|
||||
#endif // FILEHANDLER_H
|
5
QtScrcpy/device/filehandler/filehandler.pri
Normal file
5
QtScrcpy/device/filehandler/filehandler.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/filehandler.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/filehandler.cpp
|
330
QtScrcpy/device/recorder/recorder.cpp
Normal file
330
QtScrcpy/device/recorder/recorder.cpp
Normal file
|
@ -0,0 +1,330 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include "compat.h"
|
||||
#include "recorder.h"
|
||||
|
||||
static const AVRational SCRCPY_TIME_BASE = { 1, 1000000 }; // timestamps in us
|
||||
|
||||
Recorder::Recorder(const QString &fileName, QObject *parent) : QThread(parent), m_fileName(fileName), m_format(guessRecordFormat(fileName)) {}
|
||||
|
||||
Recorder::~Recorder() {}
|
||||
|
||||
AVPacket *Recorder::packetNew(const AVPacket *packet)
|
||||
{
|
||||
AVPacket *rec = new AVPacket;
|
||||
if (!rec) {
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
// av_packet_ref() does not initialize all fields in old FFmpeg versions
|
||||
av_init_packet(rec);
|
||||
|
||||
if (av_packet_ref(rec, packet)) {
|
||||
delete rec;
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
void Recorder::packetDelete(AVPacket *packet)
|
||||
{
|
||||
av_packet_unref(packet);
|
||||
delete packet;
|
||||
}
|
||||
|
||||
void Recorder::queueClear()
|
||||
{
|
||||
while (!m_queue.isEmpty()) {
|
||||
packetDelete(m_queue.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
void Recorder::setFrameSize(const QSize &declaredFrameSize)
|
||||
{
|
||||
m_declaredFrameSize = declaredFrameSize;
|
||||
}
|
||||
|
||||
void Recorder::setFormat(Recorder::RecorderFormat format)
|
||||
{
|
||||
m_format = format;
|
||||
}
|
||||
|
||||
bool Recorder::open(const AVCodec *inputCodec)
|
||||
{
|
||||
QString formatName = recorderGetFormatName(m_format);
|
||||
Q_ASSERT(!formatName.isEmpty());
|
||||
const AVOutputFormat *format = findMuxer(formatName.toUtf8());
|
||||
if (!format) {
|
||||
qCritical("Could not find muxer");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_formatCtx = avformat_alloc_context();
|
||||
if (!m_formatCtx) {
|
||||
qCritical("Could not allocate output context");
|
||||
return false;
|
||||
}
|
||||
|
||||
// contrary to the deprecated API (av_oformat_next()), av_muxer_iterate()
|
||||
// returns (on purpose) a pointer-to-const, but AVFormatContext.oformat
|
||||
// still expects a pointer-to-non-const (it has not be updated accordingly)
|
||||
// <https://github.com/FFmpeg/FFmpeg/commit/0694d8702421e7aff1340038559c438b61bb30dd>
|
||||
|
||||
m_formatCtx->oformat = (AVOutputFormat *)format;
|
||||
|
||||
QString comment = "Recorded by QtScrcpy " + QCoreApplication::applicationVersion();
|
||||
av_dict_set(&m_formatCtx->metadata, "comment", comment.toUtf8(), 0);
|
||||
|
||||
AVStream *outStream = avformat_new_stream(m_formatCtx, inputCodec);
|
||||
if (!outStream) {
|
||||
avformat_free_context(m_formatCtx);
|
||||
m_formatCtx = Q_NULLPTR;
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef QTSCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
||||
outStream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
outStream->codecpar->codec_id = inputCodec->id;
|
||||
outStream->codecpar->format = AV_PIX_FMT_YUV420P;
|
||||
outStream->codecpar->width = m_declaredFrameSize.width();
|
||||
outStream->codecpar->height = m_declaredFrameSize.height();
|
||||
#else
|
||||
outStream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
outStream->codec->codec_id = inputCodec->id;
|
||||
outStream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
|
||||
outStream->codec->width = m_declaredFrameSize.width();
|
||||
outStream->codec->height = m_declaredFrameSize.height();
|
||||
#endif
|
||||
|
||||
int ret = avio_open(&m_formatCtx->pb, m_fileName.toUtf8().toStdString().c_str(), AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
char errorbuf[255] = { 0 };
|
||||
av_strerror(ret, errorbuf, 254);
|
||||
qCritical() << QString("Failed to open output file: %1 %2").arg(errorbuf).arg(m_fileName).toUtf8().toStdString().c_str();
|
||||
// ostream will be cleaned up during context cleaning
|
||||
avformat_free_context(m_formatCtx);
|
||||
m_formatCtx = Q_NULLPTR;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Recorder::close()
|
||||
{
|
||||
if (Q_NULLPTR != m_formatCtx) {
|
||||
if (m_headerWritten) {
|
||||
int ret = av_write_trailer(m_formatCtx);
|
||||
if (ret < 0) {
|
||||
qCritical() << QString("Failed to write trailer to %1").arg(m_fileName).toUtf8().toStdString().c_str();
|
||||
m_failed = true;
|
||||
} else {
|
||||
qInfo() << QString("success record %1").arg(m_fileName).toStdString().c_str();
|
||||
}
|
||||
} else {
|
||||
// the recorded file is empty
|
||||
m_failed = true;
|
||||
}
|
||||
avio_close(m_formatCtx->pb);
|
||||
avformat_free_context(m_formatCtx);
|
||||
m_formatCtx = Q_NULLPTR;
|
||||
}
|
||||
}
|
||||
|
||||
bool Recorder::write(AVPacket *packet)
|
||||
{
|
||||
if (!m_headerWritten) {
|
||||
if (packet->pts != AV_NOPTS_VALUE) {
|
||||
qCritical("The first packet is not a config packet");
|
||||
return false;
|
||||
}
|
||||
bool ok = recorderWriteHeader(packet);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
m_headerWritten = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packet->pts == AV_NOPTS_VALUE) {
|
||||
// ignore config packets
|
||||
return true;
|
||||
}
|
||||
|
||||
recorderRescalePacket(packet);
|
||||
return av_write_frame(m_formatCtx, packet) >= 0;
|
||||
}
|
||||
|
||||
const AVOutputFormat *Recorder::findMuxer(const char *name)
|
||||
{
|
||||
#ifdef QTSCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||
void *opaque = Q_NULLPTR;
|
||||
#endif
|
||||
const AVOutputFormat *outFormat = Q_NULLPTR;
|
||||
do {
|
||||
#ifdef QTSCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||
outFormat = av_muxer_iterate(&opaque);
|
||||
#else
|
||||
outFormat = av_oformat_next(outFormat);
|
||||
#endif
|
||||
// until null or with name "name"
|
||||
} while (outFormat && strcmp(outFormat->name, name));
|
||||
return outFormat;
|
||||
}
|
||||
|
||||
bool Recorder::recorderWriteHeader(const AVPacket *packet)
|
||||
{
|
||||
AVStream *ostream = m_formatCtx->streams[0];
|
||||
quint8 *extradata = (quint8 *)av_malloc(packet->size * sizeof(quint8));
|
||||
if (!extradata) {
|
||||
qCritical("Cannot allocate extradata");
|
||||
return false;
|
||||
}
|
||||
// copy the first packet to the extra data
|
||||
memcpy(extradata, packet->data, packet->size);
|
||||
|
||||
#ifdef QTSCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
||||
ostream->codecpar->extradata = extradata;
|
||||
ostream->codecpar->extradata_size = packet->size;
|
||||
#else
|
||||
ostream->codec->extradata = extradata;
|
||||
ostream->codec->extradata_size = packet->size;
|
||||
#endif
|
||||
|
||||
int ret = avformat_write_header(m_formatCtx, NULL);
|
||||
if (ret < 0) {
|
||||
qCritical("Failed to write header recorder file");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Recorder::recorderRescalePacket(AVPacket *packet)
|
||||
{
|
||||
AVStream *ostream = m_formatCtx->streams[0];
|
||||
av_packet_rescale_ts(packet, SCRCPY_TIME_BASE, ostream->time_base);
|
||||
}
|
||||
|
||||
QString Recorder::recorderGetFormatName(Recorder::RecorderFormat format)
|
||||
{
|
||||
switch (format) {
|
||||
case RECORDER_FORMAT_MP4:
|
||||
return "mp4";
|
||||
case RECORDER_FORMAT_MKV:
|
||||
return "matroska";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Recorder::RecorderFormat Recorder::guessRecordFormat(const QString &fileName)
|
||||
{
|
||||
if (4 > fileName.length()) {
|
||||
return Recorder::RECORDER_FORMAT_NULL;
|
||||
}
|
||||
QFileInfo fileInfo = QFileInfo(fileName);
|
||||
QString ext = fileInfo.suffix();
|
||||
if (0 == ext.compare("mp4")) {
|
||||
return Recorder::RECORDER_FORMAT_MP4;
|
||||
}
|
||||
if (0 == ext.compare("mkv")) {
|
||||
return Recorder::RECORDER_FORMAT_MKV;
|
||||
}
|
||||
|
||||
return Recorder::RECORDER_FORMAT_NULL;
|
||||
}
|
||||
|
||||
void Recorder::run()
|
||||
{
|
||||
for (;;) {
|
||||
AVPacket *rec = Q_NULLPTR;
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
while (!m_stopped && m_queue.isEmpty()) {
|
||||
m_recvDataCond.wait(&m_mutex);
|
||||
}
|
||||
|
||||
// if stopped is set, continue to process the remaining events (to
|
||||
// finish the recording) before actually stopping
|
||||
if (m_stopped && m_queue.isEmpty()) {
|
||||
AVPacket *last = m_previous;
|
||||
if (last) {
|
||||
// assign an arbitrary duration to the last packet
|
||||
last->duration = 100000;
|
||||
bool ok = write(last);
|
||||
if (!ok) {
|
||||
// failing to write the last frame is not very serious, no
|
||||
// future frame may depend on it, so the resulting file
|
||||
// will still be valid
|
||||
qWarning("Could not record last packet");
|
||||
}
|
||||
packetDelete(last);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
rec = m_queue.dequeue();
|
||||
}
|
||||
|
||||
// recorder->previous is only written from this thread, no need to lock
|
||||
AVPacket *previous = m_previous;
|
||||
m_previous = rec;
|
||||
|
||||
if (!previous) {
|
||||
// we just received the first packet
|
||||
continue;
|
||||
}
|
||||
|
||||
// config packets have no PTS, we must ignore them
|
||||
if (rec->pts != AV_NOPTS_VALUE && previous->pts != AV_NOPTS_VALUE) {
|
||||
// we now know the duration of the previous packet
|
||||
previous->duration = rec->pts - previous->pts;
|
||||
}
|
||||
|
||||
bool ok = write(previous);
|
||||
packetDelete(previous);
|
||||
if (!ok) {
|
||||
qCritical("Could not record packet");
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_failed = true;
|
||||
// discard pending packets
|
||||
queueClear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug("Recorder thread ended");
|
||||
}
|
||||
|
||||
bool Recorder::startRecorder()
|
||||
{
|
||||
start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Recorder::stopRecorder()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_stopped = true;
|
||||
m_recvDataCond.wakeOne();
|
||||
}
|
||||
|
||||
bool Recorder::push(const AVPacket *packet)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
Q_ASSERT(!m_stopped);
|
||||
|
||||
if (m_failed) {
|
||||
// reject any new packet (this will stop the stream)
|
||||
return false;
|
||||
}
|
||||
|
||||
AVPacket *rec = packetNew(packet);
|
||||
if (rec) {
|
||||
m_queue.enqueue(rec);
|
||||
m_recvDataCond.wakeOne();
|
||||
}
|
||||
return rec != Q_NULLPTR;
|
||||
}
|
71
QtScrcpy/device/recorder/recorder.h
Normal file
71
QtScrcpy/device/recorder/recorder.h
Normal file
|
@ -0,0 +1,71 @@
|
|||
#ifndef RECORDER_H
|
||||
#define RECORDER_H
|
||||
#include <QMutex>
|
||||
#include <QQueue>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <QWaitCondition>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavformat/avformat.h"
|
||||
}
|
||||
|
||||
class Recorder : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum RecorderFormat
|
||||
{
|
||||
RECORDER_FORMAT_NULL = 0,
|
||||
RECORDER_FORMAT_MP4,
|
||||
RECORDER_FORMAT_MKV,
|
||||
};
|
||||
|
||||
Recorder(const QString &fileName, QObject *parent = Q_NULLPTR);
|
||||
virtual ~Recorder();
|
||||
|
||||
void setFrameSize(const QSize &declaredFrameSize);
|
||||
void setFormat(Recorder::RecorderFormat format);
|
||||
bool open(const AVCodec *inputCodec);
|
||||
void close();
|
||||
bool write(AVPacket *packet);
|
||||
bool startRecorder();
|
||||
void stopRecorder();
|
||||
bool push(const AVPacket *packet);
|
||||
|
||||
private:
|
||||
const AVOutputFormat *findMuxer(const char *name);
|
||||
bool recorderWriteHeader(const AVPacket *packet);
|
||||
void recorderRescalePacket(AVPacket *packet);
|
||||
QString recorderGetFormatName(Recorder::RecorderFormat format);
|
||||
RecorderFormat guessRecordFormat(const QString &fileName);
|
||||
|
||||
private:
|
||||
AVPacket *packetNew(const AVPacket *packet);
|
||||
void packetDelete(AVPacket *packet);
|
||||
void queueClear();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
|
||||
private:
|
||||
QString m_fileName = "";
|
||||
AVFormatContext *m_formatCtx = Q_NULLPTR;
|
||||
QSize m_declaredFrameSize;
|
||||
bool m_headerWritten = false;
|
||||
RecorderFormat m_format = RECORDER_FORMAT_NULL;
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_recvDataCond;
|
||||
bool m_stopped = false; // set on recorder_stop() by the stream reader
|
||||
bool m_failed = false; // set on packet write failure
|
||||
QQueue<AVPacket *> m_queue;
|
||||
// we can write a packet only once we received the next one so that we can
|
||||
// set its duration (next_pts - current_pts)
|
||||
// "previous" is only accessed from the recorder thread, so it does not
|
||||
// need to be protected by the mutex
|
||||
AVPacket *m_previous = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // RECORDER_H
|
5
QtScrcpy/device/recorder/recorder.pri
Normal file
5
QtScrcpy/device/recorder/recorder.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/recorder.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/recorder.cpp
|
|
@ -157,8 +157,6 @@ void QYUVOpenGLWidget::initializeGL()
|
|||
|
||||
void QYUVOpenGLWidget::paintGL()
|
||||
{
|
||||
m_shaderProgram.bind();
|
||||
|
||||
if (m_needUpdate) {
|
||||
deInitTextures();
|
||||
initTextures();
|
||||
|
@ -177,8 +175,6 @@ void QYUVOpenGLWidget::paintGL()
|
|||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
m_shaderProgram.release();
|
||||
}
|
||||
|
||||
void QYUVOpenGLWidget::resizeGL(int width, int height)
|
5
QtScrcpy/device/render/render.pri
Normal file
5
QtScrcpy/device/render/render.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/qyuvopenglwidget.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qyuvopenglwidget.cpp
|
511
QtScrcpy/device/server/server.cpp
Normal file
511
QtScrcpy/device/server/server.cpp
Normal file
|
@ -0,0 +1,511 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
#include <QTimerEvent>
|
||||
|
||||
#include "config.h"
|
||||
#include "server.h"
|
||||
|
||||
#define DEVICE_NAME_FIELD_LENGTH 64
|
||||
#define SOCKET_NAME "scrcpy"
|
||||
#define MAX_CONNECT_COUNT 30
|
||||
#define MAX_RESTART_COUNT 1
|
||||
|
||||
Server::Server(QObject *parent) : QObject(parent)
|
||||
{
|
||||
connect(&m_workProcess, &AdbProcess::adbProcessResult, this, &Server::onWorkProcessResult);
|
||||
connect(&m_serverProcess, &AdbProcess::adbProcessResult, this, &Server::onWorkProcessResult);
|
||||
|
||||
connect(&m_serverSocket, &QTcpServer::newConnection, this, [this]() {
|
||||
QTcpSocket *tmp = m_serverSocket.nextPendingConnection();
|
||||
if (dynamic_cast<VideoSocket *>(tmp)) {
|
||||
m_videoSocket = dynamic_cast<VideoSocket *>(tmp);
|
||||
if (!m_videoSocket->isValid() || !readInfo(m_videoSocket, m_deviceName, m_deviceSize)) {
|
||||
stop();
|
||||
emit connectToResult(false);
|
||||
}
|
||||
} else {
|
||||
m_controlSocket = tmp;
|
||||
if (m_controlSocket && m_controlSocket->isValid()) {
|
||||
// we don't need the server socket anymore
|
||||
// just m_videoSocket is ok
|
||||
m_serverSocket.close();
|
||||
// we don't need the adb tunnel anymore
|
||||
disableTunnelReverse();
|
||||
m_tunnelEnabled = false;
|
||||
emit connectToResult(true, m_deviceName, m_deviceSize);
|
||||
} else {
|
||||
stop();
|
||||
emit connectToResult(false);
|
||||
}
|
||||
stopAcceptTimeoutTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Server::~Server() {}
|
||||
|
||||
const QString &Server::getServerPath()
|
||||
{
|
||||
if (m_serverPath.isEmpty()) {
|
||||
m_serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
|
||||
QFileInfo fileInfo(m_serverPath);
|
||||
if (m_serverPath.isEmpty() || !fileInfo.isFile()) {
|
||||
m_serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
|
||||
}
|
||||
}
|
||||
return m_serverPath;
|
||||
}
|
||||
|
||||
bool Server::pushServer()
|
||||
{
|
||||
if (m_workProcess.isRuning()) {
|
||||
m_workProcess.kill();
|
||||
}
|
||||
m_workProcess.push(m_params.serial, getServerPath(), Config::getInstance().getServerPath());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::enableTunnelReverse()
|
||||
{
|
||||
if (m_workProcess.isRuning()) {
|
||||
m_workProcess.kill();
|
||||
}
|
||||
m_workProcess.reverse(m_params.serial, SOCKET_NAME, m_params.localPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::disableTunnelReverse()
|
||||
{
|
||||
AdbProcess *adb = new AdbProcess();
|
||||
if (!adb) {
|
||||
return false;
|
||||
}
|
||||
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
sender()->deleteLater();
|
||||
}
|
||||
});
|
||||
adb->reverseRemove(m_params.serial, SOCKET_NAME);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::enableTunnelForward()
|
||||
{
|
||||
if (m_workProcess.isRuning()) {
|
||||
m_workProcess.kill();
|
||||
}
|
||||
m_workProcess.forward(m_params.serial, m_params.localPort, SOCKET_NAME);
|
||||
return true;
|
||||
}
|
||||
bool Server::disableTunnelForward()
|
||||
{
|
||||
AdbProcess *adb = new AdbProcess();
|
||||
if (!adb) {
|
||||
return false;
|
||||
}
|
||||
connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
sender()->deleteLater();
|
||||
}
|
||||
});
|
||||
adb->forwardRemove(m_params.serial, m_params.localPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::execute()
|
||||
{
|
||||
if (m_serverProcess.isRuning()) {
|
||||
m_serverProcess.kill();
|
||||
}
|
||||
QStringList args;
|
||||
args << "shell";
|
||||
args << QString("CLASSPATH=%1").arg(Config::getInstance().getServerPath());
|
||||
args << "app_process";
|
||||
|
||||
#ifdef SERVER_DEBUGGER
|
||||
#define SERVER_DEBUGGER_PORT "5005"
|
||||
|
||||
args <<
|
||||
#ifdef SERVER_DEBUGGER_METHOD_NEW
|
||||
/* Android 9 and above */
|
||||
"-XjdwpProvider:internal -XjdwpOptions:transport=dt_socket,suspend=y,server=y,address="
|
||||
#else
|
||||
/* Android 8 and below */
|
||||
"-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address="
|
||||
#endif
|
||||
SERVER_DEBUGGER_PORT,
|
||||
#endif
|
||||
|
||||
args << "/"; // unused;
|
||||
args << "com.genymobile.scrcpy.Server";
|
||||
args << Config::getInstance().getServerVersion();
|
||||
args << Config::getInstance().getLogLevel();
|
||||
args << QString::number(m_params.maxSize);
|
||||
args << QString::number(m_params.bitRate);
|
||||
args << QString::number(m_params.maxFps);
|
||||
args << QString::number(m_params.lockVideoOrientation);
|
||||
args << (m_tunnelForward ? "true" : "false");
|
||||
if (m_params.crop.isEmpty()) {
|
||||
args << "-";
|
||||
} else {
|
||||
args << m_params.crop;
|
||||
}
|
||||
args << "true"; // always send frame meta (packet boundaries + timestamp)
|
||||
args << (m_params.control ? "true" : "false");
|
||||
args << "0"; // display id
|
||||
args << "false"; // show touch
|
||||
args << (m_params.stayAwake ? "true" : "false"); // stay awake
|
||||
// code option
|
||||
// https://github.com/Genymobile/scrcpy/commit/080a4ee3654a9b7e96c8ffe37474b5c21c02852a
|
||||
// <https://d.android.com/reference/android/media/MediaFormat>
|
||||
args << "-";
|
||||
|
||||
#ifdef SERVER_DEBUGGER
|
||||
qInfo("Server debugger waiting for a client on device port " SERVER_DEBUGGER_PORT "...");
|
||||
// From the computer, run
|
||||
// adb forward tcp:5005 tcp:5005
|
||||
// Then, from Android Studio: Run > Debug > Edit configurations...
|
||||
// On the left, click on '+', "Remote", with:
|
||||
// Host: localhost
|
||||
// Port: 5005
|
||||
// Then click on "Debug"
|
||||
#endif
|
||||
|
||||
// adb -s P7C0218510000537 shell CLASSPATH=/data/local/tmp/scrcpy-server app_process / com.genymobile.scrcpy.Server 0 8000000 false
|
||||
// mark: crop input format: "width:height:x:y" or - for no crop, for example: "100:200:0:0"
|
||||
// 这条adb命令是阻塞运行的,m_serverProcess进程不会退出了
|
||||
m_serverProcess.execute(m_params.serial, args);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::start(Server::ServerParams params)
|
||||
{
|
||||
m_params = params;
|
||||
m_serverStartStep = SSS_PUSH;
|
||||
return startServerByStep();
|
||||
}
|
||||
|
||||
bool Server::connectTo()
|
||||
{
|
||||
if (SSS_RUNNING != m_serverStartStep) {
|
||||
qWarning("server not run");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_tunnelForward && !m_videoSocket) {
|
||||
startAcceptTimeoutTimer();
|
||||
return true;
|
||||
}
|
||||
|
||||
startConnectTimeoutTimer();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Server::isReverse()
|
||||
{
|
||||
return !m_tunnelForward;
|
||||
}
|
||||
|
||||
Server::ServerParams Server::getParams()
|
||||
{
|
||||
return m_params;
|
||||
}
|
||||
|
||||
void Server::timerEvent(QTimerEvent *event)
|
||||
{
|
||||
if (event && m_acceptTimeoutTimer == event->timerId()) {
|
||||
stopAcceptTimeoutTimer();
|
||||
emit connectToResult(false, "", QSize());
|
||||
} else if (event && m_connectTimeoutTimer == event->timerId()) {
|
||||
onConnectTimer();
|
||||
}
|
||||
}
|
||||
|
||||
VideoSocket *Server::getVideoSocket()
|
||||
{
|
||||
return m_videoSocket;
|
||||
}
|
||||
|
||||
QTcpSocket *Server::getControlSocket()
|
||||
{
|
||||
return m_controlSocket;
|
||||
}
|
||||
|
||||
void Server::stop()
|
||||
{
|
||||
if (m_tunnelForward) {
|
||||
stopConnectTimeoutTimer();
|
||||
} else {
|
||||
stopAcceptTimeoutTimer();
|
||||
}
|
||||
|
||||
if (m_videoSocket) {
|
||||
m_videoSocket->close();
|
||||
m_videoSocket->deleteLater();
|
||||
}
|
||||
if (m_controlSocket) {
|
||||
m_controlSocket->close();
|
||||
m_controlSocket->deleteLater();
|
||||
}
|
||||
// ignore failure
|
||||
m_serverProcess.kill();
|
||||
if (m_tunnelEnabled) {
|
||||
if (m_tunnelForward) {
|
||||
disableTunnelForward();
|
||||
} else {
|
||||
disableTunnelReverse();
|
||||
}
|
||||
m_tunnelForward = false;
|
||||
m_tunnelEnabled = false;
|
||||
}
|
||||
m_serverSocket.close();
|
||||
}
|
||||
|
||||
bool Server::startServerByStep()
|
||||
{
|
||||
bool stepSuccess = false;
|
||||
// push, enable tunnel et start the server
|
||||
if (SSS_NULL != m_serverStartStep) {
|
||||
switch (m_serverStartStep) {
|
||||
case SSS_PUSH:
|
||||
stepSuccess = pushServer();
|
||||
break;
|
||||
case SSS_ENABLE_TUNNEL_REVERSE:
|
||||
stepSuccess = enableTunnelReverse();
|
||||
break;
|
||||
case SSS_ENABLE_TUNNEL_FORWARD:
|
||||
stepSuccess = enableTunnelForward();
|
||||
break;
|
||||
case SSS_EXECUTE_SERVER:
|
||||
// server will connect to our server socket
|
||||
stepSuccess = execute();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!stepSuccess) {
|
||||
emit serverStartResult(false);
|
||||
}
|
||||
return stepSuccess;
|
||||
}
|
||||
|
||||
bool Server::readInfo(VideoSocket *videoSocket, QString &deviceName, QSize &size)
|
||||
{
|
||||
unsigned char buf[DEVICE_NAME_FIELD_LENGTH + 4];
|
||||
if (videoSocket->bytesAvailable() <= (DEVICE_NAME_FIELD_LENGTH + 4)) {
|
||||
videoSocket->waitForReadyRead(300);
|
||||
}
|
||||
|
||||
qint64 len = videoSocket->read((char *)buf, sizeof(buf));
|
||||
if (len < DEVICE_NAME_FIELD_LENGTH + 4) {
|
||||
qInfo("Could not retrieve device information");
|
||||
return false;
|
||||
}
|
||||
buf[DEVICE_NAME_FIELD_LENGTH - 1] = '\0'; // in case the client sends garbage
|
||||
// strcpy is safe here, since name contains at least DEVICE_NAME_FIELD_LENGTH bytes
|
||||
// and strlen(buf) < DEVICE_NAME_FIELD_LENGTH
|
||||
deviceName = (char *)buf;
|
||||
size.setWidth((buf[DEVICE_NAME_FIELD_LENGTH] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 1]);
|
||||
size.setHeight((buf[DEVICE_NAME_FIELD_LENGTH + 2] << 8) | buf[DEVICE_NAME_FIELD_LENGTH + 3]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Server::startAcceptTimeoutTimer()
|
||||
{
|
||||
stopAcceptTimeoutTimer();
|
||||
m_acceptTimeoutTimer = startTimer(1000);
|
||||
}
|
||||
|
||||
void Server::stopAcceptTimeoutTimer()
|
||||
{
|
||||
if (m_acceptTimeoutTimer) {
|
||||
killTimer(m_acceptTimeoutTimer);
|
||||
m_acceptTimeoutTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Server::startConnectTimeoutTimer()
|
||||
{
|
||||
stopConnectTimeoutTimer();
|
||||
m_connectTimeoutTimer = startTimer(100);
|
||||
}
|
||||
|
||||
void Server::stopConnectTimeoutTimer()
|
||||
{
|
||||
if (m_connectTimeoutTimer) {
|
||||
killTimer(m_connectTimeoutTimer);
|
||||
m_connectTimeoutTimer = 0;
|
||||
}
|
||||
m_connectCount = 0;
|
||||
}
|
||||
|
||||
void Server::onConnectTimer()
|
||||
{
|
||||
// device server need time to start
|
||||
// 这里连接太早时间不够导致安卓监听socket还没有建立,readInfo会失败,所以采取定时重试策略
|
||||
// 每隔100ms尝试一次,最多尝试MAX_CONNECT_COUNT次
|
||||
QString deviceName;
|
||||
QSize deviceSize;
|
||||
bool success = false;
|
||||
|
||||
VideoSocket *videoSocket = new VideoSocket();
|
||||
QTcpSocket *controlSocket = new QTcpSocket();
|
||||
|
||||
videoSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort);
|
||||
if (!videoSocket->waitForConnected(1000)) {
|
||||
// 连接到adb很快的,这里失败不重试
|
||||
m_connectCount = MAX_CONNECT_COUNT;
|
||||
qWarning("video socket connect to server failed");
|
||||
goto result;
|
||||
}
|
||||
|
||||
controlSocket->connectToHost(QHostAddress::LocalHost, m_params.localPort);
|
||||
if (!controlSocket->waitForConnected(1000)) {
|
||||
// 连接到adb很快的,这里失败不重试
|
||||
m_connectCount = MAX_CONNECT_COUNT;
|
||||
qWarning("control socket connect to server failed");
|
||||
goto result;
|
||||
}
|
||||
|
||||
if (QTcpSocket::ConnectedState == videoSocket->state()) {
|
||||
// connect will success even if devices offline, recv data is real connect success
|
||||
// because connect is to pc adb server
|
||||
videoSocket->waitForReadyRead(1000);
|
||||
// devices will send 1 byte first on tunnel forward mode
|
||||
QByteArray data = videoSocket->read(1);
|
||||
if (!data.isEmpty() && readInfo(videoSocket, deviceName, deviceSize)) {
|
||||
success = true;
|
||||
goto result;
|
||||
} else {
|
||||
qWarning("video socket connect to server read device info failed, try again");
|
||||
goto result;
|
||||
}
|
||||
} else {
|
||||
qWarning("connect to server failed");
|
||||
m_connectCount = MAX_CONNECT_COUNT;
|
||||
goto result;
|
||||
}
|
||||
|
||||
result:
|
||||
if (success) {
|
||||
stopConnectTimeoutTimer();
|
||||
m_videoSocket = videoSocket;
|
||||
m_controlSocket = controlSocket;
|
||||
// we don't need the adb tunnel anymore
|
||||
disableTunnelForward();
|
||||
m_tunnelEnabled = false;
|
||||
m_restartCount = 0;
|
||||
emit connectToResult(success, deviceName, deviceSize);
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoSocket) {
|
||||
videoSocket->deleteLater();
|
||||
}
|
||||
if (controlSocket) {
|
||||
controlSocket->deleteLater();
|
||||
}
|
||||
|
||||
if (MAX_CONNECT_COUNT <= m_connectCount++) {
|
||||
stopConnectTimeoutTimer();
|
||||
stop();
|
||||
if (MAX_RESTART_COUNT > m_restartCount++) {
|
||||
qWarning("restart server auto");
|
||||
start(m_params);
|
||||
} else {
|
||||
m_restartCount = 0;
|
||||
emit connectToResult(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult)
|
||||
{
|
||||
if (sender() == &m_workProcess) {
|
||||
if (SSS_NULL != m_serverStartStep) {
|
||||
switch (m_serverStartStep) {
|
||||
case SSS_PUSH:
|
||||
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
|
||||
if (m_params.useReverse) {
|
||||
m_serverStartStep = SSS_ENABLE_TUNNEL_REVERSE;
|
||||
} else {
|
||||
m_tunnelForward = true;
|
||||
m_serverStartStep = SSS_ENABLE_TUNNEL_FORWARD;
|
||||
}
|
||||
startServerByStep();
|
||||
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
qCritical("adb push failed");
|
||||
m_serverStartStep = SSS_NULL;
|
||||
emit serverStartResult(false);
|
||||
}
|
||||
break;
|
||||
case SSS_ENABLE_TUNNEL_REVERSE:
|
||||
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
|
||||
// At the application level, the device part is "the server" because it
|
||||
// serves video stream and control. However, at the network level, the
|
||||
// client listens and the server connects to the client. That way, the
|
||||
// client can listen before starting the server app, so there is no need to
|
||||
// try to connect until the server socket is listening on the device.
|
||||
m_serverSocket.setMaxPendingConnections(2);
|
||||
if (!m_serverSocket.listen(QHostAddress::LocalHost, m_params.localPort)) {
|
||||
qCritical() << QString("Could not listen on port %1").arg(m_params.localPort).toStdString().c_str();
|
||||
m_serverStartStep = SSS_NULL;
|
||||
disableTunnelReverse();
|
||||
emit serverStartResult(false);
|
||||
break;
|
||||
}
|
||||
|
||||
m_serverStartStep = SSS_EXECUTE_SERVER;
|
||||
startServerByStep();
|
||||
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
// 有一些设备reverse会报错more than o'ne device,adb的bug
|
||||
// https://github.com/Genymobile/scrcpy/issues/5
|
||||
qCritical("adb reverse failed");
|
||||
m_tunnelForward = true;
|
||||
m_serverStartStep = SSS_ENABLE_TUNNEL_FORWARD;
|
||||
startServerByStep();
|
||||
}
|
||||
break;
|
||||
case SSS_ENABLE_TUNNEL_FORWARD:
|
||||
if (AdbProcess::AER_SUCCESS_EXEC == processResult) {
|
||||
m_serverStartStep = SSS_EXECUTE_SERVER;
|
||||
startServerByStep();
|
||||
} else if (AdbProcess::AER_SUCCESS_START != processResult) {
|
||||
qCritical("adb forward failed");
|
||||
m_serverStartStep = SSS_NULL;
|
||||
emit serverStartResult(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sender() == &m_serverProcess) {
|
||||
if (SSS_EXECUTE_SERVER == m_serverStartStep) {
|
||||
if (AdbProcess::AER_SUCCESS_START == processResult) {
|
||||
m_serverStartStep = SSS_RUNNING;
|
||||
m_tunnelEnabled = true;
|
||||
emit serverStartResult(true);
|
||||
} else if (AdbProcess::AER_ERROR_START == processResult) {
|
||||
if (!m_tunnelForward) {
|
||||
m_serverSocket.close();
|
||||
disableTunnelReverse();
|
||||
} else {
|
||||
disableTunnelForward();
|
||||
}
|
||||
qCritical("adb shell start server failed");
|
||||
m_serverStartStep = SSS_NULL;
|
||||
emit serverStartResult(false);
|
||||
}
|
||||
} else if (SSS_RUNNING == m_serverStartStep) {
|
||||
m_serverStartStep = SSS_NULL;
|
||||
emit onServerStop();
|
||||
}
|
||||
}
|
||||
}
|
101
QtScrcpy/device/server/server.h
Normal file
101
QtScrcpy/device/server/server.h
Normal file
|
@ -0,0 +1,101 @@
|
|||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QSize>
|
||||
|
||||
#include "adbprocess.h"
|
||||
#include "tcpserver.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
class Server : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
enum SERVER_START_STEP
|
||||
{
|
||||
SSS_NULL,
|
||||
SSS_PUSH,
|
||||
SSS_ENABLE_TUNNEL_REVERSE,
|
||||
SSS_ENABLE_TUNNEL_FORWARD,
|
||||
SSS_EXECUTE_SERVER,
|
||||
SSS_RUNNING,
|
||||
};
|
||||
|
||||
public:
|
||||
struct ServerParams
|
||||
{
|
||||
QString serial = ""; // 设备序列号
|
||||
quint16 localPort = 27183; // reverse时本地监听端口
|
||||
quint16 maxSize = 720; // 视频分辨率
|
||||
quint32 bitRate = 8000000; // 视频比特率
|
||||
quint32 maxFps = 60; // 视频最大帧率
|
||||
QString crop = "-"; // 视频裁剪
|
||||
bool control = true; // 安卓端是否接收键鼠控制
|
||||
bool useReverse = true; // true:先使用adb reverse,失败后自动使用adb forward;false:直接使用adb forward
|
||||
int lockVideoOrientation = -1; // 是否锁定视频方向
|
||||
int stayAwake = false; // 是否保持唤醒
|
||||
};
|
||||
|
||||
explicit Server(QObject *parent = nullptr);
|
||||
virtual ~Server();
|
||||
|
||||
bool start(Server::ServerParams params);
|
||||
bool connectTo();
|
||||
bool isReverse();
|
||||
Server::ServerParams getParams();
|
||||
|
||||
VideoSocket *getVideoSocket();
|
||||
QTcpSocket *getControlSocket();
|
||||
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
void serverStartResult(bool success);
|
||||
void connectToResult(bool success, const QString &deviceName = "", const QSize &size = QSize());
|
||||
void onServerStop();
|
||||
|
||||
private slots:
|
||||
void onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *event);
|
||||
|
||||
private:
|
||||
const QString &getServerPath();
|
||||
bool pushServer();
|
||||
bool enableTunnelReverse();
|
||||
bool disableTunnelReverse();
|
||||
bool enableTunnelForward();
|
||||
bool disableTunnelForward();
|
||||
bool execute();
|
||||
bool startServerByStep();
|
||||
bool readInfo(VideoSocket *videoSocket, QString &deviceName, QSize &size);
|
||||
void startAcceptTimeoutTimer();
|
||||
void stopAcceptTimeoutTimer();
|
||||
void startConnectTimeoutTimer();
|
||||
void stopConnectTimeoutTimer();
|
||||
void onConnectTimer();
|
||||
|
||||
private:
|
||||
QString m_serverPath = "";
|
||||
AdbProcess m_workProcess;
|
||||
AdbProcess m_serverProcess;
|
||||
TcpServer m_serverSocket; // only used if !tunnel_forward
|
||||
QPointer<VideoSocket> m_videoSocket = Q_NULLPTR;
|
||||
QPointer<QTcpSocket> m_controlSocket = Q_NULLPTR;
|
||||
bool m_tunnelEnabled = false;
|
||||
bool m_tunnelForward = false; // use "adb forward" instead of "adb reverse"
|
||||
int m_acceptTimeoutTimer = 0;
|
||||
int m_connectTimeoutTimer = 0;
|
||||
quint32 m_connectCount = 0;
|
||||
quint32 m_restartCount = 0;
|
||||
QString m_deviceName = "";
|
||||
QSize m_deviceSize = QSize();
|
||||
ServerParams m_params;
|
||||
|
||||
SERVER_START_STEP m_serverStartStep = SSS_NULL;
|
||||
};
|
||||
|
||||
#endif // SERVER_H
|
9
QtScrcpy/device/server/server.pri
Normal file
9
QtScrcpy/device/server/server.pri
Normal file
|
@ -0,0 +1,9 @@
|
|||
HEADERS += \
|
||||
$$PWD/server.h \
|
||||
$$PWD/tcpserver.h \
|
||||
$$PWD/videosocket.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/server.cpp \
|
||||
$$PWD/tcpserver.cpp \
|
||||
$$PWD/videosocket.cpp
|
22
QtScrcpy/device/server/tcpserver.cpp
Normal file
22
QtScrcpy/device/server/tcpserver.cpp
Normal file
|
@ -0,0 +1,22 @@
|
|||
#include "tcpserver.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
TcpServer::TcpServer(QObject *parent) : QTcpServer(parent) {}
|
||||
|
||||
TcpServer::~TcpServer() {}
|
||||
|
||||
void TcpServer::incomingConnection(qintptr handle)
|
||||
{
|
||||
if (m_isVideoSocket) {
|
||||
VideoSocket *socket = new VideoSocket();
|
||||
socket->setSocketDescriptor(handle);
|
||||
addPendingConnection(socket);
|
||||
|
||||
// next is control socket
|
||||
m_isVideoSocket = false;
|
||||
} else {
|
||||
QTcpSocket *socket = new QTcpSocket();
|
||||
socket->setSocketDescriptor(handle);
|
||||
addPendingConnection(socket);
|
||||
}
|
||||
}
|
20
QtScrcpy/device/server/tcpserver.h
Normal file
20
QtScrcpy/device/server/tcpserver.h
Normal file
|
@ -0,0 +1,20 @@
|
|||
#ifndef TCPSERVER_H
|
||||
#define TCPSERVER_H
|
||||
|
||||
#include <QTcpServer>
|
||||
|
||||
class TcpServer : public QTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit TcpServer(QObject *parent = nullptr);
|
||||
virtual ~TcpServer();
|
||||
|
||||
protected:
|
||||
virtual void incomingConnection(qintptr handle);
|
||||
|
||||
private:
|
||||
bool m_isVideoSocket = true;
|
||||
};
|
||||
|
||||
#endif // TCPSERVER_H
|
81
QtScrcpy/device/server/videosocket.cpp
Normal file
81
QtScrcpy/device/server/videosocket.cpp
Normal file
|
@ -0,0 +1,81 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QThread>
|
||||
|
||||
#include "qscrcpyevent.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
VideoSocket::VideoSocket(QObject *parent) : QTcpSocket(parent)
|
||||
{
|
||||
connect(this, &VideoSocket::readyRead, this, &VideoSocket::onReadyRead);
|
||||
connect(this, &VideoSocket::aboutToClose, this, &VideoSocket::quitNotify);
|
||||
connect(this, &VideoSocket::disconnected, this, &VideoSocket::quitNotify);
|
||||
}
|
||||
|
||||
VideoSocket::~VideoSocket()
|
||||
{
|
||||
quitNotify();
|
||||
}
|
||||
|
||||
qint32 VideoSocket::subThreadRecvData(quint8 *buf, qint32 bufSize)
|
||||
{
|
||||
// this function cant call in main thread
|
||||
Q_ASSERT(QCoreApplication::instance()->thread() != QThread::currentThread());
|
||||
if (m_quit) {
|
||||
return 0;
|
||||
}
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
m_buffer = buf;
|
||||
m_bufferSize = bufSize;
|
||||
m_dataSize = 0;
|
||||
|
||||
// post event
|
||||
VideoSocketEvent *getDataEvent = new VideoSocketEvent();
|
||||
QCoreApplication::postEvent(this, getDataEvent);
|
||||
|
||||
// wait
|
||||
while (!m_recvData) {
|
||||
m_recvDataCond.wait(&m_mutex);
|
||||
}
|
||||
|
||||
m_recvData = false;
|
||||
return m_dataSize;
|
||||
}
|
||||
|
||||
bool VideoSocket::event(QEvent *event)
|
||||
{
|
||||
if (static_cast<QScrcpyEvent::Type>(event->type()) == QScrcpyEvent::VideoSocket) {
|
||||
onReadyRead();
|
||||
return true;
|
||||
}
|
||||
return QTcpSocket::event(event);
|
||||
}
|
||||
|
||||
void VideoSocket::onReadyRead()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_buffer && m_bufferSize <= bytesAvailable()) {
|
||||
// recv data
|
||||
qint64 readSize = qMin(bytesAvailable(), (qint64)m_bufferSize);
|
||||
m_dataSize = read((char *)m_buffer, readSize);
|
||||
|
||||
m_buffer = Q_NULLPTR;
|
||||
m_bufferSize = 0;
|
||||
m_recvData = true;
|
||||
m_recvDataCond.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSocket::quitNotify()
|
||||
{
|
||||
m_quit = true;
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_buffer) {
|
||||
m_buffer = Q_NULLPTR;
|
||||
m_bufferSize = 0;
|
||||
m_recvData = true;
|
||||
m_dataSize = 0;
|
||||
m_recvDataCond.wakeOne();
|
||||
}
|
||||
}
|
35
QtScrcpy/device/server/videosocket.h
Normal file
35
QtScrcpy/device/server/videosocket.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
#ifndef VIDEOSOCKET_H
|
||||
#define VIDEOSOCKET_H
|
||||
|
||||
#include <QEvent>
|
||||
#include <QMutex>
|
||||
#include <QTcpSocket>
|
||||
#include <QWaitCondition>
|
||||
|
||||
class VideoSocket : public QTcpSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VideoSocket(QObject *parent = nullptr);
|
||||
virtual ~VideoSocket();
|
||||
|
||||
qint32 subThreadRecvData(quint8 *buf, qint32 bufSize);
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event);
|
||||
|
||||
protected slots:
|
||||
void onReadyRead();
|
||||
void quitNotify();
|
||||
|
||||
private:
|
||||
QMutex m_mutex;
|
||||
QWaitCondition m_recvDataCond;
|
||||
bool m_recvData = false;
|
||||
quint8 *m_buffer = Q_NULLPTR;
|
||||
qint32 m_bufferSize = 0;
|
||||
qint32 m_dataSize = 0;
|
||||
bool m_quit = false;
|
||||
};
|
||||
|
||||
#endif // VIDEOSOCKET_H
|
361
QtScrcpy/device/stream/stream.cpp
Normal file
361
QtScrcpy/device/stream/stream.cpp
Normal file
|
@ -0,0 +1,361 @@
|
|||
#include <QDebug>
|
||||
#include <QTime>
|
||||
|
||||
#include "compat.h"
|
||||
#include "decoder.h"
|
||||
#include "recorder.h"
|
||||
#include "stream.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
#define BUFSIZE 0x10000
|
||||
#define HEADER_SIZE 12
|
||||
#define NO_PTS UINT64_MAX
|
||||
|
||||
typedef qint32 (*ReadPacketFunc)(void *, quint8 *, qint32);
|
||||
|
||||
Stream::Stream(QObject *parent) : QThread(parent) {}
|
||||
|
||||
Stream::~Stream() {}
|
||||
|
||||
static void avLogCallback(void *avcl, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
Q_UNUSED(avcl)
|
||||
Q_UNUSED(vl)
|
||||
|
||||
QString localFmt = QString::fromUtf8(fmt);
|
||||
localFmt.prepend("[FFmpeg] ");
|
||||
switch (level) {
|
||||
case AV_LOG_PANIC:
|
||||
case AV_LOG_FATAL:
|
||||
qFatal("%s", localFmt.toUtf8().data());
|
||||
break;
|
||||
case AV_LOG_ERROR:
|
||||
qCritical() << localFmt.toUtf8();
|
||||
break;
|
||||
case AV_LOG_WARNING:
|
||||
qWarning() << localFmt.toUtf8();
|
||||
break;
|
||||
case AV_LOG_INFO:
|
||||
qInfo() << localFmt.toUtf8();
|
||||
break;
|
||||
case AV_LOG_DEBUG:
|
||||
// qDebug() << localFmt.toUtf8();
|
||||
break;
|
||||
}
|
||||
|
||||
// do not forward others, which are too verbose
|
||||
return;
|
||||
}
|
||||
|
||||
bool Stream::init()
|
||||
{
|
||||
#ifdef QTSCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
av_register_all();
|
||||
#endif
|
||||
if (avformat_network_init()) {
|
||||
return false;
|
||||
}
|
||||
av_log_set_callback(avLogCallback);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stream::deInit()
|
||||
{
|
||||
avformat_network_deinit(); // ignore failure
|
||||
}
|
||||
|
||||
void Stream::setDecoder(Decoder *decoder)
|
||||
{
|
||||
m_decoder = decoder;
|
||||
}
|
||||
|
||||
static quint32 bufferRead32be(quint8 *buf)
|
||||
{
|
||||
return static_cast<quint32>((buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]);
|
||||
}
|
||||
|
||||
static quint64 bufferRead64be(quint8 *buf)
|
||||
{
|
||||
quint32 msb = bufferRead32be(buf);
|
||||
quint32 lsb = bufferRead32be(&buf[4]);
|
||||
return (static_cast<quint64>(msb) << 32) | lsb;
|
||||
}
|
||||
|
||||
void Stream::setVideoSocket(VideoSocket *videoSocket)
|
||||
{
|
||||
m_videoSocket = videoSocket;
|
||||
}
|
||||
|
||||
void Stream::setRecoder(Recorder *recorder)
|
||||
{
|
||||
m_recorder = recorder;
|
||||
}
|
||||
|
||||
qint32 Stream::recvData(quint8 *buf, qint32 bufSize)
|
||||
{
|
||||
if (!buf) {
|
||||
return 0;
|
||||
}
|
||||
if (m_videoSocket) {
|
||||
qint32 len = m_videoSocket->subThreadRecvData(buf, bufSize);
|
||||
return len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool Stream::startDecode()
|
||||
{
|
||||
if (!m_videoSocket) {
|
||||
return false;
|
||||
}
|
||||
start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stream::stopDecode()
|
||||
{
|
||||
if (m_decoder) {
|
||||
m_decoder->interrupt();
|
||||
}
|
||||
wait();
|
||||
}
|
||||
|
||||
void Stream::run()
|
||||
{
|
||||
AVCodec *codec = Q_NULLPTR;
|
||||
m_codecCtx = Q_NULLPTR;
|
||||
m_parser = Q_NULLPTR;
|
||||
|
||||
// codec
|
||||
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (!codec) {
|
||||
qCritical("H.264 decoder not found");
|
||||
goto runQuit;
|
||||
}
|
||||
|
||||
// codeCtx
|
||||
m_codecCtx = avcodec_alloc_context3(codec);
|
||||
if (!m_codecCtx) {
|
||||
qCritical("Could not allocate codec context");
|
||||
goto runQuit;
|
||||
}
|
||||
|
||||
if (m_decoder && !m_decoder->open(codec)) {
|
||||
qCritical("Could not open m_decoder");
|
||||
goto runQuit;
|
||||
}
|
||||
|
||||
if (m_recorder) {
|
||||
if (!m_recorder->open(codec)) {
|
||||
qCritical("Could not open recorder");
|
||||
goto runQuit;
|
||||
}
|
||||
|
||||
if (!m_recorder->startRecorder()) {
|
||||
qCritical("Could not start recorder");
|
||||
goto runQuit;
|
||||
}
|
||||
}
|
||||
|
||||
m_parser = av_parser_init(AV_CODEC_ID_H264);
|
||||
if (!m_parser) {
|
||||
qCritical("Could not initialize parser");
|
||||
goto runQuit;
|
||||
}
|
||||
|
||||
// We must only pass complete frames to av_parser_parse2()!
|
||||
// It's more complicated, but this allows to reduce the latency by 1 frame!
|
||||
m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES;
|
||||
|
||||
for (;;) {
|
||||
AVPacket packet;
|
||||
bool ok = recvPacket(&packet);
|
||||
if (!ok) {
|
||||
// end of stream
|
||||
break;
|
||||
}
|
||||
|
||||
ok = pushPacket(&packet);
|
||||
av_packet_unref(&packet);
|
||||
if (!ok) {
|
||||
// cannot process packet (error already logged)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug("End of frames");
|
||||
|
||||
if (m_hasPending) {
|
||||
av_packet_unref(&m_pending);
|
||||
}
|
||||
|
||||
av_parser_close(m_parser);
|
||||
|
||||
runQuit:
|
||||
if (m_recorder) {
|
||||
if (m_recorder->isRunning()) {
|
||||
m_recorder->stopRecorder();
|
||||
m_recorder->wait();
|
||||
}
|
||||
m_recorder->close();
|
||||
}
|
||||
if (m_decoder) {
|
||||
m_decoder->close();
|
||||
}
|
||||
if (m_codecCtx) {
|
||||
avcodec_free_context(&m_codecCtx);
|
||||
}
|
||||
|
||||
emit onStreamStop();
|
||||
}
|
||||
|
||||
bool Stream::recvPacket(AVPacket *packet)
|
||||
{
|
||||
// The video stream contains raw packets, without time information. When we
|
||||
// record, we retrieve the timestamps separately, from a "meta" header
|
||||
// added by the server before each raw packet.
|
||||
//
|
||||
// The "meta" header length is 12 bytes:
|
||||
// [. . . . . . . .|. . . .]. . . . . . . . . . . . . . . ...
|
||||
// <-------------> <-----> <-----------------------------...
|
||||
// PTS packet raw packet
|
||||
// size
|
||||
//
|
||||
// It is followed by <packet_size> bytes containing the packet/frame.
|
||||
|
||||
quint8 header[HEADER_SIZE];
|
||||
qint32 r = recvData(header, HEADER_SIZE);
|
||||
if (r < HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 pts = bufferRead64be(header);
|
||||
quint32 len = bufferRead32be(&header[8]);
|
||||
Q_ASSERT(pts == NO_PTS || (pts & 0x8000000000000000) == 0);
|
||||
Q_ASSERT(len);
|
||||
|
||||
if (av_new_packet(packet, static_cast<int>(len))) {
|
||||
qCritical("Could not allocate packet");
|
||||
return false;
|
||||
}
|
||||
|
||||
r = recvData(packet->data, static_cast<qint32>(len));
|
||||
if (r < 0 || static_cast<quint32>(r) < len) {
|
||||
av_packet_unref(packet);
|
||||
return false;
|
||||
}
|
||||
|
||||
packet->pts = pts != NO_PTS ? static_cast<int64_t>(pts) : static_cast<int64_t>(AV_NOPTS_VALUE);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stream::pushPacket(AVPacket *packet)
|
||||
{
|
||||
bool isConfig = packet->pts == AV_NOPTS_VALUE;
|
||||
|
||||
// A config packet must not be decoded immetiately (it contains no
|
||||
// frame); instead, it must be concatenated with the future data packet.
|
||||
if (m_hasPending || isConfig) {
|
||||
qint32 offset;
|
||||
if (m_hasPending) {
|
||||
offset = m_pending.size;
|
||||
if (av_grow_packet(&m_pending, packet->size)) {
|
||||
qCritical("Could not grow packet");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
offset = 0;
|
||||
if (av_new_packet(&m_pending, packet->size)) {
|
||||
qCritical("Could not create packet");
|
||||
return false;
|
||||
}
|
||||
m_hasPending = true;
|
||||
}
|
||||
|
||||
memcpy(m_pending.data + offset, packet->data, static_cast<unsigned int>(packet->size));
|
||||
|
||||
if (!isConfig) {
|
||||
// prepare the concat packet to send to the decoder
|
||||
m_pending.pts = packet->pts;
|
||||
m_pending.dts = packet->dts;
|
||||
m_pending.flags = packet->flags;
|
||||
packet = &m_pending;
|
||||
}
|
||||
}
|
||||
|
||||
if (isConfig) {
|
||||
// config packet
|
||||
bool ok = processConfigPacket(packet);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// data packet
|
||||
bool ok = parse(packet);
|
||||
|
||||
if (m_hasPending) {
|
||||
// the pending packet must be discarded (consumed or error)
|
||||
m_hasPending = false;
|
||||
av_packet_unref(&m_pending);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stream::processConfigPacket(AVPacket *packet)
|
||||
{
|
||||
if (m_recorder && !m_recorder->push(packet)) {
|
||||
qCritical("Could not send config packet to recorder");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stream::parse(AVPacket *packet)
|
||||
{
|
||||
quint8 *inData = packet->data;
|
||||
int inLen = packet->size;
|
||||
quint8 *outData = Q_NULLPTR;
|
||||
int outLen = 0;
|
||||
int r = av_parser_parse2(m_parser, m_codecCtx, &outData, &outLen, inData, inLen, AV_NOPTS_VALUE, AV_NOPTS_VALUE, -1);
|
||||
|
||||
// PARSER_FLAG_COMPLETE_FRAMES is set
|
||||
Q_ASSERT(r == inLen);
|
||||
(void)r;
|
||||
Q_ASSERT(outLen == inLen);
|
||||
|
||||
if (m_parser->key_frame == 1) {
|
||||
packet->flags |= AV_PKT_FLAG_KEY;
|
||||
}
|
||||
|
||||
bool ok = processFrame(packet);
|
||||
if (!ok) {
|
||||
qCritical("Could not process frame");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Stream::processFrame(AVPacket *packet)
|
||||
{
|
||||
if (m_decoder && !m_decoder->push(packet)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_recorder) {
|
||||
packet->dts = packet->pts;
|
||||
|
||||
if (!m_recorder->push(packet)) {
|
||||
qCritical("Could not send packet to recorder");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
59
QtScrcpy/device/stream/stream.h
Normal file
59
QtScrcpy/device/stream/stream.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
#ifndef STREAM_H
|
||||
#define STREAM_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QThread>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
}
|
||||
|
||||
class VideoSocket;
|
||||
class Recorder;
|
||||
class Decoder;
|
||||
class Stream : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Stream(QObject *parent = Q_NULLPTR);
|
||||
virtual ~Stream();
|
||||
|
||||
public:
|
||||
static bool init();
|
||||
static void deInit();
|
||||
|
||||
void setDecoder(Decoder *decoder);
|
||||
void setRecoder(Recorder *recorder);
|
||||
void setVideoSocket(VideoSocket *deviceSocket);
|
||||
qint32 recvData(quint8 *buf, qint32 bufSize);
|
||||
bool startDecode();
|
||||
void stopDecode();
|
||||
|
||||
signals:
|
||||
void onStreamStop();
|
||||
|
||||
protected:
|
||||
void run();
|
||||
bool recvPacket(AVPacket *packet);
|
||||
bool pushPacket(AVPacket *packet);
|
||||
bool processConfigPacket(AVPacket *packet);
|
||||
bool parse(AVPacket *packet);
|
||||
bool processFrame(AVPacket *packet);
|
||||
|
||||
private:
|
||||
QPointer<VideoSocket> m_videoSocket;
|
||||
// for recorder
|
||||
Recorder *m_recorder = Q_NULLPTR;
|
||||
Decoder *m_decoder = Q_NULLPTR;
|
||||
|
||||
AVCodecContext *m_codecCtx = Q_NULLPTR;
|
||||
AVCodecParserContext *m_parser = Q_NULLPTR;
|
||||
// successive packets may need to be concatenated, until a non-config
|
||||
// packet is available
|
||||
bool m_hasPending = false;
|
||||
AVPacket m_pending;
|
||||
};
|
||||
|
||||
#endif // STREAM_H
|
6
QtScrcpy/device/stream/stream.pri
Normal file
6
QtScrcpy/device/stream/stream.pri
Normal file
|
@ -0,0 +1,6 @@
|
|||
HEADERS += \
|
||||
$$PWD/stream.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/stream.cpp
|
||||
|
|
@ -3,11 +3,10 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QShowEvent>
|
||||
|
||||
#include "device.h"
|
||||
#include "iconhelper.h"
|
||||
#include "toolform.h"
|
||||
#include "ui_toolform.h"
|
||||
#include "videoform.h"
|
||||
#include "../groupcontroller/groupcontroller.h"
|
||||
|
||||
ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticWidget(adsorbWidget, adsorbPos), ui(new Ui::ToolForm)
|
||||
{
|
||||
|
@ -15,8 +14,6 @@ ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticW
|
|||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
//setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint);
|
||||
|
||||
updateGroupControl();
|
||||
|
||||
initStyle();
|
||||
}
|
||||
|
||||
|
@ -25,14 +22,13 @@ ToolForm::~ToolForm()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void ToolForm::setSerial(const QString &serial)
|
||||
void ToolForm::setDevice(Device *device)
|
||||
{
|
||||
m_serial = serial;
|
||||
}
|
||||
|
||||
bool ToolForm::isHost()
|
||||
{
|
||||
return m_isHost;
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
m_device = device;
|
||||
connect(m_device, &Device::controlStateChange, this, &ToolForm::onControlStateChange);
|
||||
}
|
||||
|
||||
void ToolForm::initStyle()
|
||||
|
@ -45,7 +41,6 @@ void ToolForm::initStyle()
|
|||
IconHelper::Instance()->SetIcon(ui->appSwitchBtn, QChar(0xf24d), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->volumeUpBtn, QChar(0xf028), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->volumeDownBtn, QChar(0xf027), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->openScreenBtn, QChar(0xf06e), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->closeScreenBtn, QChar(0xf070), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->powerBtn, QChar(0xf011), 15);
|
||||
IconHelper::Instance()->SetIcon(ui->expandNotifyBtn, QChar(0xf103), 15);
|
||||
|
@ -56,23 +51,26 @@ void ToolForm::initStyle()
|
|||
|
||||
void ToolForm::updateGroupControl()
|
||||
{
|
||||
if (m_isHost) {
|
||||
ui->groupControlBtn->setStyleSheet("color: red");
|
||||
} else {
|
||||
ui->groupControlBtn->setStyleSheet("color: green");
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
switch (m_device->controlState()) {
|
||||
case Device::GroupControlState::GCS_FREE:
|
||||
ui->groupControlBtn->setStyleSheet("color: #DCDCDC");
|
||||
break;
|
||||
case Device::GroupControlState::GCS_HOST:
|
||||
ui->groupControlBtn->setStyleSheet("color: red");
|
||||
break;
|
||||
case Device::GroupControlState::GCS_CLIENT:
|
||||
ui->groupControlBtn->setStyleSheet("color: green");
|
||||
break;
|
||||
}
|
||||
|
||||
GroupController::instance().updateDeviceState(m_serial);
|
||||
}
|
||||
|
||||
void ToolForm::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
|
||||
#else
|
||||
m_dragPosition = event->globalPosition().toPoint() - frameGeometry().topLeft();
|
||||
#endif
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
@ -85,11 +83,7 @@ void ToolForm::mouseReleaseEvent(QMouseEvent *event)
|
|||
void ToolForm::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
move(event->globalPos() - m_dragPosition);
|
||||
#else
|
||||
move(event->globalPosition().toPoint() - m_dragPosition);
|
||||
#endif
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
@ -108,126 +102,121 @@ void ToolForm::hideEvent(QHideEvent *event)
|
|||
|
||||
void ToolForm::on_fullScreenBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
|
||||
dynamic_cast<VideoForm*>(parent())->switchFullScreen();
|
||||
emit m_device->switchFullScreen();
|
||||
}
|
||||
|
||||
void ToolForm::on_returnBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoBack();
|
||||
emit m_device->postGoBack();
|
||||
}
|
||||
|
||||
void ToolForm::on_homeBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoHome();
|
||||
emit m_device->postGoHome();
|
||||
}
|
||||
|
||||
void ToolForm::on_menuBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoMenu();
|
||||
emit m_device->postGoMenu();
|
||||
}
|
||||
|
||||
void ToolForm::on_appSwitchBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postAppSwitch();
|
||||
emit m_device->postAppSwitch();
|
||||
}
|
||||
|
||||
void ToolForm::on_powerBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postPower();
|
||||
emit m_device->postPower();
|
||||
}
|
||||
|
||||
void ToolForm::on_screenShotBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->screenshot();
|
||||
emit m_device->screenshot();
|
||||
}
|
||||
|
||||
void ToolForm::on_volumeUpBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postVolumeUp();
|
||||
emit m_device->postVolumeUp();
|
||||
}
|
||||
|
||||
void ToolForm::on_volumeDownBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postVolumeDown();
|
||||
emit m_device->postVolumeDown();
|
||||
}
|
||||
|
||||
void ToolForm::on_closeScreenBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->setDisplayPower(false);
|
||||
emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF);
|
||||
}
|
||||
|
||||
void ToolForm::on_expandNotifyBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->expandNotificationPanel();
|
||||
emit m_device->expandNotificationPanel();
|
||||
}
|
||||
|
||||
void ToolForm::on_touchBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_showTouch = !m_showTouch;
|
||||
device->showTouch(m_showTouch);
|
||||
emit m_device->showTouch(m_showTouch);
|
||||
}
|
||||
|
||||
void ToolForm::on_groupControlBtn_clicked()
|
||||
{
|
||||
m_isHost = !m_isHost;
|
||||
updateGroupControl();
|
||||
}
|
||||
|
||||
void ToolForm::on_openScreenBtn_clicked()
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->setDisplayPower(true);
|
||||
Device::GroupControlState state = m_device->controlState();
|
||||
if (state == Device::GroupControlState::GCS_FREE) {
|
||||
emit m_device->setControlState(m_device, Device::GroupControlState::GCS_HOST);
|
||||
}
|
||||
if (state == Device::GroupControlState::GCS_HOST) {
|
||||
emit m_device->setControlState(m_device, Device::GroupControlState::GCS_FREE);
|
||||
}
|
||||
}
|
||||
|
||||
void ToolForm::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState)
|
||||
{
|
||||
Q_UNUSED(device)
|
||||
Q_UNUSED(oldState)
|
||||
Q_UNUSED(newState)
|
||||
updateGroupControl();
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
#include "device.h"
|
||||
#include "magneticwidget.h"
|
||||
|
||||
namespace Ui
|
||||
|
@ -21,8 +21,7 @@ public:
|
|||
explicit ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos);
|
||||
~ToolForm();
|
||||
|
||||
void setSerial(const QString& serial);
|
||||
bool isHost();
|
||||
void setDevice(Device *device);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
|
@ -46,7 +45,8 @@ private slots:
|
|||
void on_expandNotifyBtn_clicked();
|
||||
void on_touchBtn_clicked();
|
||||
void on_groupControlBtn_clicked();
|
||||
void on_openScreenBtn_clicked();
|
||||
|
||||
void onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
|
||||
|
||||
private:
|
||||
void initStyle();
|
||||
|
@ -55,9 +55,8 @@ private:
|
|||
private:
|
||||
Ui::ToolForm *ui;
|
||||
QPoint m_dragPosition;
|
||||
QString m_serial;
|
||||
QPointer<Device> m_device;
|
||||
bool m_showTouch = false;
|
||||
bool m_isHost = false;
|
||||
};
|
||||
|
||||
#endif // TOOLFORM_H
|
|
@ -22,9 +22,6 @@
|
|||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="groupControlBtn">
|
||||
<property name="toolTip">
|
||||
<string>group control</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
|
@ -73,16 +70,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="openScreenBtn">
|
||||
<property name="toolTip">
|
||||
<string>open screen</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="closeScreenBtn">
|
||||
<property name="toolTip">
|
11
QtScrcpy/device/ui/ui.pri
Normal file
11
QtScrcpy/device/ui/ui.pri
Normal file
|
@ -0,0 +1,11 @@
|
|||
SOURCES += \
|
||||
$$PWD/videoform.cpp \
|
||||
$$PWD/toolform.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/videoform.h \
|
||||
$$PWD/toolform.h
|
||||
|
||||
FORMS += \
|
||||
$$PWD/videoform.ui \
|
||||
$$PWD/toolform.ui
|
|
@ -1,4 +1,4 @@
|
|||
// #include <QDesktopWidget>
|
||||
#include <QDesktopWidget>
|
||||
#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
@ -13,26 +13,26 @@
|
|||
#include <QWindow>
|
||||
#include <QtWidgets/QHBoxLayout>
|
||||
|
||||
#if defined(Q_OS_WIN32)
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
#include "controller.h"
|
||||
#include "device.h"
|
||||
#include "iconhelper.h"
|
||||
#include "qyuvopenglwidget.h"
|
||||
#include "toolform.h"
|
||||
#include "mousetap/mousetap.h"
|
||||
#include "ui_videoform.h"
|
||||
#include "videoform.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavutil/frame.h"
|
||||
}
|
||||
|
||||
VideoForm::VideoForm(bool framelessWindow, bool skin, bool showToolbar, QWidget *parent) : QWidget(parent), ui(new Ui::videoForm), m_skin(skin)
|
||||
VideoForm::VideoForm(bool framelessWindow, bool skin, QWidget *parent) : QWidget(parent), ui(new Ui::videoForm), m_skin(skin)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
initUI();
|
||||
installShortcut();
|
||||
updateShowSize(size());
|
||||
bool vertical = size().height() > size().width();
|
||||
this->show_toolbar = showToolbar;
|
||||
if (m_skin) {
|
||||
updateStyleSheet(vertical);
|
||||
}
|
||||
|
@ -48,6 +48,7 @@ VideoForm::~VideoForm()
|
|||
|
||||
void VideoForm::initUI()
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (m_skin) {
|
||||
QPixmap phone;
|
||||
if (phone.load(":/res/phone.png")) {
|
||||
|
@ -65,8 +66,8 @@ void VideoForm::initUI()
|
|||
|
||||
m_videoWidget = new QYUVOpenGLWidget();
|
||||
m_videoWidget->hide();
|
||||
ui->keepRatioWidget->setWidget(m_videoWidget);
|
||||
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
|
||||
ui->keepRadioWidget->setWidget(m_videoWidget);
|
||||
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
|
||||
|
||||
m_fpsLabel = new QLabel(m_videoWidget);
|
||||
QFont ft;
|
||||
|
@ -80,17 +81,17 @@ void VideoForm::initUI()
|
|||
|
||||
setMouseTracking(true);
|
||||
m_videoWidget->setMouseTracking(true);
|
||||
ui->keepRatioWidget->setMouseTracking(true);
|
||||
ui->keepRadioWidget->setMouseTracking(true);
|
||||
}
|
||||
|
||||
QRect VideoForm::getGrabCursorRect()
|
||||
{
|
||||
QRect rc;
|
||||
#if defined(Q_OS_WIN32)
|
||||
rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
|
||||
rc = QRect(ui->keepRadioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
|
||||
// high dpi support
|
||||
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatioF());
|
||||
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatioF());
|
||||
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatio());
|
||||
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatio());
|
||||
|
||||
rc.setX(rc.x() + 10);
|
||||
rc.setY(rc.y() + 10);
|
||||
|
@ -98,18 +99,18 @@ QRect VideoForm::getGrabCursorRect()
|
|||
rc.setHeight(rc.height() - 20);
|
||||
#elif defined(Q_OS_OSX)
|
||||
rc = m_videoWidget->geometry();
|
||||
rc.setTopLeft(ui->keepRatioWidget->mapToGlobal(rc.topLeft()));
|
||||
rc.setBottomRight(ui->keepRatioWidget->mapToGlobal(rc.bottomRight()));
|
||||
rc.setTopLeft(ui->keepRadioWidget->mapToGlobal(rc.topLeft()));
|
||||
rc.setBottomRight(ui->keepRadioWidget->mapToGlobal(rc.bottomRight()));
|
||||
|
||||
rc.setX(rc.x() + 10);
|
||||
rc.setY(rc.y() + 10);
|
||||
rc.setWidth(rc.width() - 20);
|
||||
rc.setHeight(rc.height() - 20);
|
||||
#elif defined(Q_OS_LINUX)
|
||||
rc = QRect(ui->keepRatioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
|
||||
rc = QRect(ui->keepRadioWidget->mapToGlobal(m_videoWidget->pos()), m_videoWidget->size());
|
||||
// high dpi support -- taken from the WIN32 section and untested
|
||||
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatioF());
|
||||
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatioF());
|
||||
rc.setTopLeft(rc.topLeft() * m_videoWidget->devicePixelRatio());
|
||||
rc.setBottomRight(rc.bottomRight() * m_videoWidget->devicePixelRatio());
|
||||
|
||||
rc.setX(rc.x() + 10);
|
||||
rc.setY(rc.y() + 10);
|
||||
|
@ -136,7 +137,7 @@ void VideoForm::resizeSquare()
|
|||
|
||||
void VideoForm::removeBlackRect()
|
||||
{
|
||||
resize(ui->keepRatioWidget->goodSize());
|
||||
resize(ui->keepRadioWidget->goodSize());
|
||||
}
|
||||
|
||||
void VideoForm::showFPS(bool show)
|
||||
|
@ -147,7 +148,7 @@ void VideoForm::showFPS(bool show)
|
|||
m_fpsLabel->setVisible(show);
|
||||
}
|
||||
|
||||
void VideoForm::updateRender(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV)
|
||||
void VideoForm::updateRender(const AVFrame *frame)
|
||||
{
|
||||
if (m_videoWidget->isHidden()) {
|
||||
if (m_loadingWidget) {
|
||||
|
@ -156,21 +157,16 @@ void VideoForm::updateRender(int width, int height, uint8_t* dataY, uint8_t* dat
|
|||
m_videoWidget->show();
|
||||
}
|
||||
|
||||
updateShowSize(QSize(width, height));
|
||||
m_videoWidget->setFrameSize(QSize(width, height));
|
||||
m_videoWidget->updateTextures(dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);
|
||||
}
|
||||
|
||||
void VideoForm::setSerial(const QString &serial)
|
||||
{
|
||||
m_serial = serial;
|
||||
updateShowSize(QSize(frame->width, frame->height));
|
||||
m_videoWidget->setFrameSize(QSize(frame->width, frame->height));
|
||||
m_videoWidget->updateTextures(frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], frame->linesize[1], frame->linesize[2]);
|
||||
}
|
||||
|
||||
void VideoForm::showToolForm(bool show)
|
||||
{
|
||||
if (!m_toolForm) {
|
||||
m_toolForm = new ToolForm(this, ToolForm::AP_OUTSIDE_RIGHT);
|
||||
m_toolForm->setSerial(m_serial);
|
||||
m_toolForm->setDevice(m_device);
|
||||
}
|
||||
m_toolForm->move(pos().x() + geometry().width(), pos().y() + 30);
|
||||
m_toolForm->setVisible(show);
|
||||
|
@ -193,192 +189,157 @@ void VideoForm::installShortcut()
|
|||
|
||||
// switchFullScreen
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+f"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
switchFullScreen();
|
||||
emit m_device->switchFullScreen();
|
||||
});
|
||||
|
||||
// resizeSquare
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+g"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() { resizeSquare(); });
|
||||
|
||||
// removeBlackRect
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+w"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+x"), this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() { removeBlackRect(); });
|
||||
|
||||
// postGoHome
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+h"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoHome();
|
||||
emit m_device->postGoHome();
|
||||
});
|
||||
|
||||
// postGoBack
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+b"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoBack();
|
||||
emit m_device->postGoBack();
|
||||
});
|
||||
|
||||
// postAppSwitch
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+s"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->postAppSwitch();
|
||||
emit m_device->postAppSwitch();
|
||||
});
|
||||
|
||||
// postGoMenu
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+m"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
device->postGoMenu();
|
||||
emit m_device->postGoMenu();
|
||||
});
|
||||
|
||||
// postVolumeUp
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+up"), this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->postVolumeUp();
|
||||
emit m_device->postVolumeUp();
|
||||
});
|
||||
|
||||
// postVolumeDown
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+down"), this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->postVolumeDown();
|
||||
emit m_device->postVolumeDown();
|
||||
});
|
||||
|
||||
// postPower
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+p"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->postPower();
|
||||
emit m_device->postPower();
|
||||
});
|
||||
|
||||
// setScreenPowerMode(ControlMsg::SPM_OFF)
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+o"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->setDisplayPower(false);
|
||||
emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF);
|
||||
});
|
||||
|
||||
// expandNotificationPanel
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+n"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->expandNotificationPanel();
|
||||
emit m_device->expandNotificationPanel();
|
||||
});
|
||||
|
||||
// collapsePanel
|
||||
// collapseNotificationPanel
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+Shift+n"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->collapsePanel();
|
||||
emit m_device->collapseNotificationPanel();
|
||||
});
|
||||
|
||||
// copy
|
||||
// requestDeviceClipboard
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+c"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->postCopy();
|
||||
});
|
||||
|
||||
// cut
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+x"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit device->postCut();
|
||||
emit m_device->requestDeviceClipboard();
|
||||
});
|
||||
|
||||
// clipboardPaste
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+v"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->setDeviceClipboard();
|
||||
emit m_device->clipboardPaste();
|
||||
});
|
||||
|
||||
// setDeviceClipboard
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+Shift+v"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->clipboardPaste();
|
||||
emit m_device->setDeviceClipboard();
|
||||
});
|
||||
}
|
||||
|
||||
QRect VideoForm::getScreenRect()
|
||||
{
|
||||
QRect screenRect;
|
||||
QScreen *screen = QGuiApplication::primaryScreen();
|
||||
QWidget *win = window();
|
||||
if (win) {
|
||||
QWindow *winHandle = win->windowHandle();
|
||||
if (winHandle) {
|
||||
screen = winHandle->screen();
|
||||
}
|
||||
if (!win) {
|
||||
return screenRect;
|
||||
}
|
||||
|
||||
if (screen) {
|
||||
screenRect = screen->availableGeometry();
|
||||
QWindow *winHandle = win->windowHandle();
|
||||
QScreen *screen = QGuiApplication::primaryScreen();
|
||||
if (winHandle) {
|
||||
screen = winHandle->screen();
|
||||
}
|
||||
if (!screen) {
|
||||
return screenRect;
|
||||
}
|
||||
|
||||
screenRect = screen->availableGeometry();
|
||||
return screenRect;
|
||||
}
|
||||
|
||||
|
@ -419,7 +380,7 @@ void VideoForm::updateShowSize(const QSize &newSize)
|
|||
m_frameSize = newSize;
|
||||
|
||||
m_widthHeightRatio = 1.0f * newSize.width() / newSize.height();
|
||||
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
|
||||
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
|
||||
|
||||
bool vertical = m_widthHeightRatio < 1.0f ? true : false;
|
||||
QSize showSize = newSize;
|
||||
|
@ -436,8 +397,8 @@ void VideoForm::updateShowSize(const QSize &newSize)
|
|||
showSize.setHeight(showSize.width() / m_widthHeightRatio);
|
||||
}
|
||||
|
||||
if (isFullScreen() && qsc::IDeviceManage::getInstance().getDevice(m_serial)) {
|
||||
switchFullScreen();
|
||||
if (isFullScreen() && m_device) {
|
||||
emit m_device->switchFullScreen();
|
||||
}
|
||||
|
||||
if (isMaximized()) {
|
||||
|
@ -460,17 +421,15 @@ void VideoForm::updateShowSize(const QSize &newSize)
|
|||
}
|
||||
}
|
||||
|
||||
void VideoForm::switchFullScreen()
|
||||
void VideoForm::onSwitchFullScreen()
|
||||
{
|
||||
if (isFullScreen()) {
|
||||
// 横屏全屏铺满全屏,恢复时,恢复保持宽高比
|
||||
if (m_widthHeightRatio > 1.0f) {
|
||||
ui->keepRatioWidget->setWidthHeightRatio(m_widthHeightRatio);
|
||||
ui->keepRadioWidget->setWidthHeightRadio(m_widthHeightRatio);
|
||||
}
|
||||
|
||||
showNormal();
|
||||
// back to normal size.
|
||||
resize(m_normalSize);
|
||||
// fullscreen window will move (0,0). qt bug?
|
||||
move(m_fullScreenBeforePos);
|
||||
|
||||
|
@ -481,19 +440,16 @@ void VideoForm::switchFullScreen()
|
|||
if (m_skin) {
|
||||
updateStyleSheet(m_frameSize.height() > m_frameSize.width());
|
||||
}
|
||||
showToolForm(this->show_toolbar);
|
||||
showToolForm(true);
|
||||
#ifdef Q_OS_WIN32
|
||||
::SetThreadExecutionState(ES_CONTINUOUS);
|
||||
#endif
|
||||
} else {
|
||||
// 横屏全屏铺满全屏,不保持宽高比
|
||||
if (m_widthHeightRatio > 1.0f) {
|
||||
ui->keepRatioWidget->setWidthHeightRatio(-1.0f);
|
||||
ui->keepRadioWidget->setWidthHeightRadio(-1.0f);
|
||||
}
|
||||
|
||||
// record current size before fullscreen, it will be used to rollback size after exit fullscreen.
|
||||
m_normalSize = size();
|
||||
|
||||
m_fullScreenBeforePos = pos();
|
||||
// 这种临时增加标题栏再全屏的方案会导致收不到mousemove事件,导致setmousetrack失效
|
||||
// mac fullscreen must show title bar
|
||||
|
@ -513,14 +469,6 @@ void VideoForm::switchFullScreen()
|
|||
}
|
||||
}
|
||||
|
||||
bool VideoForm::isHost()
|
||||
{
|
||||
if (!m_toolForm) {
|
||||
return false;
|
||||
}
|
||||
return m_toolForm->isHost();
|
||||
}
|
||||
|
||||
void VideoForm::updateFPS(quint32 fps)
|
||||
{
|
||||
//qDebug() << "FPS:" << fps;
|
||||
|
@ -530,17 +478,6 @@ void VideoForm::updateFPS(quint32 fps)
|
|||
m_fpsLabel->setText(QString("FPS:%1").arg(fps));
|
||||
}
|
||||
|
||||
void VideoForm::grabCursor(bool grab)
|
||||
{
|
||||
QRect rc = getGrabCursorRect();
|
||||
MouseTap::getInstance()->enableMouseEventTap(rc, grab);
|
||||
}
|
||||
|
||||
void VideoForm::onFrame(int width, int height, uint8_t *dataY, uint8_t *dataU, uint8_t *dataV, int linesizeY, int linesizeU, int linesizeV)
|
||||
{
|
||||
updateRender(width, height, dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);
|
||||
}
|
||||
|
||||
void VideoForm::staysOnTop(bool top)
|
||||
{
|
||||
bool needShow = false;
|
||||
|
@ -556,49 +493,36 @@ void VideoForm::staysOnTop(bool top)
|
|||
}
|
||||
}
|
||||
|
||||
void VideoForm::setDevice(Device *device)
|
||||
{
|
||||
m_device = device;
|
||||
}
|
||||
|
||||
void VideoForm::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (event->button() == Qt::MiddleButton) {
|
||||
if (device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoHome();
|
||||
return;
|
||||
if (m_device) {
|
||||
emit m_device->postGoHome();
|
||||
}
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton) {
|
||||
if (device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
QPointF localPos = event->localPos();
|
||||
QPointF globalPos = event->globalPos();
|
||||
#else
|
||||
QPointF localPos = event->position();
|
||||
QPointF globalPos = event->globalPosition();
|
||||
#endif
|
||||
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());
|
||||
QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());
|
||||
emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
|
||||
// debug keymap pos
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
qreal x = localPos.x() / m_videoWidget->size().width();
|
||||
qreal y = localPos.y() / m_videoWidget->size().height();
|
||||
qreal x = event->localPos().x() / m_videoWidget->size().width();
|
||||
qreal y = event->localPos().y() / m_videoWidget->size().height();
|
||||
QString posTip = QString(R"("pos": {"x": %1, "y": %2})").arg(x).arg(y);
|
||||
qInfo() << posTip.toStdString().c_str();
|
||||
}
|
||||
} else {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
m_dragPosition = globalPos.toPoint() - frameGeometry().topLeft();
|
||||
m_dragPosition = event->globalPos() - frameGeometry().topLeft();
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
@ -606,20 +530,13 @@ void VideoForm::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (m_dragPosition.isNull()) {
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
QPointF localPos = event->localPos();
|
||||
QPointF globalPos = event->globalPos();
|
||||
#else
|
||||
QPointF localPos = event->position();
|
||||
QPointF globalPos = event->globalPosition();
|
||||
#endif
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
// local check
|
||||
QPointF local = m_videoWidget->mapFrom(this, localPos.toPoint());
|
||||
QPointF local = event->localPos();
|
||||
if (local.x() < 0) {
|
||||
local.setX(0);
|
||||
}
|
||||
|
@ -632,8 +549,8 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
|||
if (local.y() > m_videoWidget->height()) {
|
||||
local.setY(m_videoWidget->height());
|
||||
}
|
||||
QMouseEvent newEvent(event->type(), local, globalPos, event->button(), event->buttons(), event->modifiers());
|
||||
emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
event->setLocalPos(local);
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
} else {
|
||||
m_dragPosition = QPoint(0, 0);
|
||||
}
|
||||
|
@ -641,24 +558,15 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
|||
|
||||
void VideoForm::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
QPointF localPos = event->localPos();
|
||||
QPointF globalPos = event->globalPos();
|
||||
#else
|
||||
QPointF localPos = event->position();
|
||||
QPointF globalPos = event->globalPosition();
|
||||
#endif
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());
|
||||
QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());
|
||||
emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
} else if (!m_dragPosition.isNull()) {
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
move(globalPos.toPoint() - m_dragPosition);
|
||||
move(event->globalPos() - m_dragPosition);
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
@ -666,91 +574,67 @@ void VideoForm::mouseMoveEvent(QMouseEvent *event)
|
|||
|
||||
void VideoForm::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (event->button() == Qt::LeftButton && !m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!isMaximized()) {
|
||||
removeBlackRect();
|
||||
}
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton && device && !device->isCurrentCustomKeymap()) {
|
||||
emit device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);
|
||||
if (event->button() == Qt::RightButton && m_device) {
|
||||
emit m_device->postBackOrScreenOn();
|
||||
}
|
||||
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
QPointF localPos = event->localPos();
|
||||
QPointF globalPos = event->globalPos();
|
||||
#else
|
||||
QPointF localPos = event->position();
|
||||
QPointF globalPos = event->globalPosition();
|
||||
#endif
|
||||
QPointF mappedPos = m_videoWidget->mapFrom(this, localPos.toPoint());
|
||||
QMouseEvent newEvent(event->type(), mappedPos, globalPos, event->button(), event->buttons(), event->modifiers());
|
||||
emit device->mouseEvent(&newEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
}
|
||||
|
||||
void VideoForm::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
if (m_videoWidget->geometry().contains(event->position().toPoint())) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF pos = m_videoWidget->mapFrom(this, event->position().toPoint());
|
||||
QWheelEvent wheelEvent(
|
||||
pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(), event->buttons(), event->modifiers(), event->phase(), event->inverted());
|
||||
#else
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
QPointF pos = m_videoWidget->mapFrom(this, event->pos());
|
||||
|
||||
QWheelEvent wheelEvent(
|
||||
pos, event->globalPosF(), event->pixelDelta(), event->angleDelta(), event->delta(), event->orientation(),
|
||||
event->buttons(), event->modifiers(), event->phase(), event->source(), event->inverted());
|
||||
#endif
|
||||
emit device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
/*
|
||||
QWheelEvent(const QPointF &pos, const QPointF& globalPos, int delta,
|
||||
Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers,
|
||||
Qt::Orientation orient = Qt::Vertical);
|
||||
*/
|
||||
QWheelEvent wheelEvent(pos, event->globalPosF(), event->delta(), event->buttons(), event->modifiers(), event->orientation());
|
||||
emit m_device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
}
|
||||
|
||||
void VideoForm::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
if (Qt::Key_Escape == event->key() && !event->isAutoRepeat() && isFullScreen()) {
|
||||
switchFullScreen();
|
||||
emit m_device->switchFullScreen();
|
||||
}
|
||||
|
||||
emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
|
||||
void VideoForm::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
|
||||
void VideoForm::paintEvent(QPaintEvent *paint)
|
||||
{
|
||||
Q_UNUSED(paint)
|
||||
QStyleOption opt;
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
opt.init(this);
|
||||
#else
|
||||
opt.initFrom(this);
|
||||
#endif
|
||||
QPainter p(this);
|
||||
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
|
||||
}
|
||||
|
@ -758,22 +642,20 @@ void VideoForm::paintEvent(QPaintEvent *paint)
|
|||
void VideoForm::showEvent(QShowEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
if (!isFullScreen() && this->show_toolbar) {
|
||||
QTimer::singleShot(500, this, [this](){
|
||||
showToolForm(this->show_toolbar);
|
||||
});
|
||||
if (!isFullScreen()) {
|
||||
showToolForm();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoForm::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
QSize goodSize = ui->keepRatioWidget->goodSize();
|
||||
QSize goodSize = ui->keepRadioWidget->goodSize();
|
||||
if (goodSize.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QSize curSize = size();
|
||||
// 限制VideoForm尺寸不能小于keepRatioWidget good size
|
||||
// 限制VideoForm尺寸不能小于keepRadioWidget good size
|
||||
if (m_widthHeightRatio > 1.0f) {
|
||||
// hor
|
||||
if (curSize.height() <= goodSize.height()) {
|
||||
|
@ -794,12 +676,10 @@ void VideoForm::resizeEvent(QResizeEvent *event)
|
|||
void VideoForm::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
Config::getInstance().setRect(device->getSerial(), geometry());
|
||||
device->disconnectDevice();
|
||||
Config::getInstance().setRect(m_device->getSerial(), geometry());
|
||||
}
|
||||
|
||||
void VideoForm::dragEnterEvent(QDragEnterEvent *event)
|
||||
|
@ -819,26 +699,21 @@ void VideoForm::dragLeaveEvent(QDragLeaveEvent *event)
|
|||
|
||||
void VideoForm::dropEvent(QDropEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
const QMimeData *qm = event->mimeData();
|
||||
QList<QUrl> urls = qm->urls();
|
||||
QString file = qm->urls()[0].toLocalFile();
|
||||
QFileInfo fileInfo(file);
|
||||
|
||||
for (const QUrl &url : urls) {
|
||||
QString file = url.toLocalFile();
|
||||
QFileInfo fileInfo(file);
|
||||
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::warning(this, "QtScrcpy", tr("file does not exist"), QMessageBox::Ok);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit device->installApkRequest(file);
|
||||
continue;
|
||||
}
|
||||
emit device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
if (!fileInfo.exists()) {
|
||||
QMessageBox::warning(this, "QtScrcpy", tr("file does not exist"), QMessageBox::Ok);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit m_device->installApkRequest(file);
|
||||
return;
|
||||
}
|
||||
emit m_device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
}
|
87
QtScrcpy/device/ui/videoform.h
Normal file
87
QtScrcpy/device/ui/videoform.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#ifndef VIDEOFORM_H
|
||||
#define VIDEOFORM_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class videoForm;
|
||||
}
|
||||
|
||||
struct AVFrame;
|
||||
class ToolForm;
|
||||
class Device;
|
||||
class FileHandler;
|
||||
class QYUVOpenGLWidget;
|
||||
class QLabel;
|
||||
class VideoForm : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VideoForm(bool framelessWindow = false, bool skin = true, QWidget *parent = 0);
|
||||
~VideoForm();
|
||||
|
||||
void staysOnTop(bool top = true);
|
||||
void updateShowSize(const QSize &newSize);
|
||||
void updateRender(const AVFrame *frame);
|
||||
void setDevice(Device *device);
|
||||
QRect getGrabCursorRect();
|
||||
const QSize &frameSize();
|
||||
void resizeSquare();
|
||||
void removeBlackRect();
|
||||
void showFPS(bool show);
|
||||
|
||||
public slots:
|
||||
void onSwitchFullScreen();
|
||||
void updateFPS(quint32 fps);
|
||||
|
||||
private:
|
||||
void updateStyleSheet(bool vertical);
|
||||
QMargins getMargins(bool vertical);
|
||||
void initUI();
|
||||
|
||||
void showToolForm(bool show = true);
|
||||
void moveCenter();
|
||||
void installShortcut();
|
||||
QRect getScreenRect();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void mouseDoubleClickEvent(QMouseEvent *event);
|
||||
void wheelEvent(QWheelEvent *event);
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void keyReleaseEvent(QKeyEvent *event);
|
||||
|
||||
void paintEvent(QPaintEvent *);
|
||||
void showEvent(QShowEvent *event);
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
void closeEvent(QCloseEvent *event);
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
void dragMoveEvent(QDragMoveEvent *event);
|
||||
void dragLeaveEvent(QDragLeaveEvent *event);
|
||||
void dropEvent(QDropEvent *event);
|
||||
|
||||
private:
|
||||
// ui
|
||||
Ui::videoForm *ui;
|
||||
QPointer<ToolForm> m_toolForm;
|
||||
QPointer<QWidget> m_loadingWidget;
|
||||
QPointer<QYUVOpenGLWidget> m_videoWidget;
|
||||
QPointer<QLabel> m_fpsLabel;
|
||||
|
||||
//inside member
|
||||
QSize m_frameSize;
|
||||
QPoint m_dragPosition;
|
||||
float m_widthHeightRatio = 0.5f;
|
||||
bool m_skin = true;
|
||||
QPoint m_fullScreenBeforePos;
|
||||
|
||||
//outside member
|
||||
QPointer<Device> m_device;
|
||||
};
|
||||
|
||||
#endif // VIDEOFORM_H
|
|
@ -39,15 +39,15 @@
|
|||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="KeepRatioWidget" name="keepRatioWidget" native="true"/>
|
||||
<widget class="KeepRadioWidget" name="keepRadioWidget" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>KeepRatioWidget</class>
|
||||
<class>KeepRadioWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">keepratiowidget.h</header>
|
||||
<header location="global">keepradiowidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
299
QtScrcpy/devicemanage/devicemanage.cpp
Normal file
299
QtScrcpy/devicemanage/devicemanage.cpp
Normal file
|
@ -0,0 +1,299 @@
|
|||
#include <QDebug>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "devicemanage.h"
|
||||
#include "server.h"
|
||||
#include "videoform.h"
|
||||
|
||||
#define DM_MAX_DEVICES_NUM 16
|
||||
|
||||
DeviceManage::DeviceManage(QObject *parent) : QObject(parent) {}
|
||||
|
||||
DeviceManage::~DeviceManage() {}
|
||||
|
||||
bool DeviceManage::connectDevice(Device::DeviceParams params)
|
||||
{
|
||||
if (params.serial.trimmed().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (m_devices.contains(params.serial)) {
|
||||
return false;
|
||||
}
|
||||
if (DM_MAX_DEVICES_NUM < m_devices.size()) {
|
||||
qInfo("over the maximum number of connections");
|
||||
return false;
|
||||
}
|
||||
/*
|
||||
// 没有必要分配端口,都用27183即可,连接建立以后server会释放监听的
|
||||
quint16 port = 0;
|
||||
if (params.useReverse) {
|
||||
port = getFreePort();
|
||||
if (0 == port) {
|
||||
qInfo("no port available, automatically switch to forward");
|
||||
params.useReverse = false;
|
||||
} else {
|
||||
params.localPort = port;
|
||||
qInfo("free port %d", port);
|
||||
}
|
||||
}
|
||||
*/
|
||||
Device *device = new Device(params);
|
||||
connect(device, &Device::deviceDisconnect, this, &DeviceManage::onDeviceDisconnect);
|
||||
connect(device, &Device::controlStateChange, this, &DeviceManage::onControlStateChange);
|
||||
m_devices[params.serial] = device;
|
||||
if (!m_script.isEmpty()) {
|
||||
device->updateScript(m_script);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceManage::updateScript(QString script)
|
||||
{
|
||||
m_script = script;
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (i.value()) {
|
||||
i.value()->updateScript(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DeviceManage::staysOnTop(const QString &serial)
|
||||
{
|
||||
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
||||
auto it = m_devices.find(serial);
|
||||
if (!it->data()) {
|
||||
return false;
|
||||
}
|
||||
if (!it->data()->getVideoForm()) {
|
||||
return false;
|
||||
}
|
||||
it->data()->getVideoForm()->staysOnTop();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceManage::showFPS(const QString &serial, bool show)
|
||||
{
|
||||
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
||||
auto it = m_devices.find(serial);
|
||||
if (!it->data()) {
|
||||
return;
|
||||
}
|
||||
if (!it->data()->getVideoForm()) {
|
||||
return;
|
||||
}
|
||||
it->data()->getVideoForm()->showFPS(show);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool DeviceManage::disconnectDevice(const QString &serial)
|
||||
{
|
||||
bool ret = false;
|
||||
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
||||
auto it = m_devices.find(serial);
|
||||
if (it->data()) {
|
||||
delete it->data();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DeviceManage::disconnectAllDevice()
|
||||
{
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (i.value()) {
|
||||
delete i.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::setGroupControlSignals(Device *host, Device *client, bool install)
|
||||
{
|
||||
if (!host || !client) {
|
||||
return;
|
||||
}
|
||||
if (install) {
|
||||
connect(host, &Device::postGoBack, client, &Device::postGoBack);
|
||||
connect(host, &Device::postGoHome, client, &Device::postGoHome);
|
||||
connect(host, &Device::postGoMenu, client, &Device::postGoMenu);
|
||||
connect(host, &Device::postAppSwitch, client, &Device::postAppSwitch);
|
||||
connect(host, &Device::postPower, client, &Device::postPower);
|
||||
connect(host, &Device::postVolumeUp, client, &Device::postVolumeUp);
|
||||
connect(host, &Device::postVolumeDown, client, &Device::postVolumeDown);
|
||||
connect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode);
|
||||
connect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel);
|
||||
connect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel);
|
||||
connect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn);
|
||||
connect(host, &Device::postTextInput, client, &Device::postTextInput);
|
||||
connect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard);
|
||||
connect(host, &Device::clipboardPaste, client, &Device::clipboardPaste);
|
||||
connect(host, &Device::pushFileRequest, client, &Device::pushFileRequest);
|
||||
connect(host, &Device::installApkRequest, client, &Device::installApkRequest);
|
||||
connect(host, &Device::screenshot, client, &Device::screenshot);
|
||||
connect(host, &Device::showTouch, client, &Device::showTouch);
|
||||
// dont connect requestDeviceClipboard
|
||||
//connect(host, &Device::requestDeviceClipboard, client, &Device::requestDeviceClipboard);
|
||||
} else {
|
||||
disconnect(host, &Device::postGoBack, client, &Device::postGoBack);
|
||||
disconnect(host, &Device::postGoHome, client, &Device::postGoHome);
|
||||
disconnect(host, &Device::postGoMenu, client, &Device::postGoMenu);
|
||||
disconnect(host, &Device::postAppSwitch, client, &Device::postAppSwitch);
|
||||
disconnect(host, &Device::postPower, client, &Device::postPower);
|
||||
disconnect(host, &Device::postVolumeUp, client, &Device::postVolumeUp);
|
||||
disconnect(host, &Device::postVolumeDown, client, &Device::postVolumeDown);
|
||||
disconnect(host, &Device::setScreenPowerMode, client, &Device::setScreenPowerMode);
|
||||
disconnect(host, &Device::expandNotificationPanel, client, &Device::expandNotificationPanel);
|
||||
disconnect(host, &Device::collapseNotificationPanel, client, &Device::collapseNotificationPanel);
|
||||
disconnect(host, &Device::postBackOrScreenOn, client, &Device::postBackOrScreenOn);
|
||||
disconnect(host, &Device::postTextInput, client, &Device::postTextInput);
|
||||
disconnect(host, &Device::setDeviceClipboard, client, &Device::setDeviceClipboard);
|
||||
disconnect(host, &Device::clipboardPaste, client, &Device::clipboardPaste);
|
||||
disconnect(host, &Device::pushFileRequest, client, &Device::pushFileRequest);
|
||||
disconnect(host, &Device::installApkRequest, client, &Device::installApkRequest);
|
||||
disconnect(host, &Device::screenshot, client, &Device::screenshot);
|
||||
disconnect(host, &Device::showTouch, client, &Device::showTouch);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::setGroupControlHost(Device *host, bool install)
|
||||
{
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (!i.value()) {
|
||||
continue;
|
||||
}
|
||||
if (i.value() == host) {
|
||||
continue;
|
||||
}
|
||||
if (install) {
|
||||
if (host) {
|
||||
setGroupControlSignals(host, i.value(), true);
|
||||
}
|
||||
emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_CLIENT);
|
||||
} else {
|
||||
if (host) {
|
||||
setGroupControlSignals(host, i.value(), false);
|
||||
}
|
||||
emit i.value()->setControlState(i.value(), Device::GroupControlState::GCS_FREE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::onDeviceDisconnect(QString serial)
|
||||
{
|
||||
if (!serial.isEmpty() && m_devices.contains(serial)) {
|
||||
if (m_devices[serial]->controlState() == Device::GroupControlState::GCS_HOST) {
|
||||
setGroupControlHost(nullptr, false);
|
||||
}
|
||||
m_devices.remove(serial);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
// free to host
|
||||
if (oldState == Device::GroupControlState::GCS_FREE && newState == Device::GroupControlState::GCS_HOST) {
|
||||
// install direct control signals
|
||||
setGroupControlHost(device, true);
|
||||
// install convert control signals(frameSize need convert)
|
||||
connect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent, Qt::UniqueConnection);
|
||||
connect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent, Qt::UniqueConnection);
|
||||
connect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent, Qt::UniqueConnection);
|
||||
return;
|
||||
}
|
||||
// host to free
|
||||
if (oldState == Device::GroupControlState::GCS_HOST && newState == Device::GroupControlState::GCS_FREE) {
|
||||
// uninstall direct control signals
|
||||
setGroupControlHost(device, false);
|
||||
// uninstall convert control signals(frameSize need convert)
|
||||
disconnect(device, &Device::mouseEvent, this, &DeviceManage::onMouseEvent);
|
||||
disconnect(device, &Device::wheelEvent, this, &DeviceManage::onWheelEvent);
|
||||
disconnect(device, &Device::keyEvent, this, &DeviceManage::onKeyEvent);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
Q_UNUSED(frameSize)
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (!i.value()) {
|
||||
continue;
|
||||
}
|
||||
if (i.value() == sender()) {
|
||||
continue;
|
||||
}
|
||||
// neend convert frameSize to its frameSize
|
||||
emit i.value()->mouseEvent(from, i.value()->frameSize(), showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
Q_UNUSED(frameSize)
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (!i.value()) {
|
||||
continue;
|
||||
}
|
||||
if (i.value() == sender()) {
|
||||
continue;
|
||||
}
|
||||
// neend convert frameSize to its frameSize
|
||||
emit i.value()->wheelEvent(from, i.value()->frameSize(), showSize);
|
||||
}
|
||||
}
|
||||
|
||||
void DeviceManage::onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
Q_UNUSED(frameSize)
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
if (!i.value()) {
|
||||
continue;
|
||||
}
|
||||
if (i.value() == sender()) {
|
||||
continue;
|
||||
}
|
||||
// neend convert frameSize to its frameSize
|
||||
emit i.value()->keyEvent(from, i.value()->frameSize(), showSize);
|
||||
}
|
||||
}
|
||||
|
||||
quint16 DeviceManage::getFreePort()
|
||||
{
|
||||
quint16 port = m_localPortStart;
|
||||
while (port < m_localPortStart + DM_MAX_DEVICES_NUM) {
|
||||
bool used = false;
|
||||
QMapIterator<QString, QPointer<Device>> i(m_devices);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
auto device = i.value();
|
||||
if (device && device->getServer() && device->getServer()->isReverse() && port == device->getServer()->getParams().localPort) {
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!used) {
|
||||
return port;
|
||||
}
|
||||
port++;
|
||||
}
|
||||
return 0;
|
||||
}
|
46
QtScrcpy/devicemanage/devicemanage.h
Normal file
46
QtScrcpy/devicemanage/devicemanage.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
#ifndef DEVICEMANAGE_H
|
||||
#define DEVICEMANAGE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QPointer>
|
||||
|
||||
#include "device.h"
|
||||
|
||||
class DeviceManage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit DeviceManage(QObject *parent = nullptr);
|
||||
virtual ~DeviceManage();
|
||||
|
||||
bool connectDevice(Device::DeviceParams params);
|
||||
void updateScript(QString script);
|
||||
bool staysOnTop(const QString &serial);
|
||||
void showFPS(const QString &serial, bool show);
|
||||
|
||||
bool disconnectDevice(const QString &serial);
|
||||
void disconnectAllDevice();
|
||||
|
||||
protected:
|
||||
void setGroupControlSignals(Device *host, Device *client, bool install);
|
||||
void setGroupControlHost(Device *host, bool install);
|
||||
|
||||
protected slots:
|
||||
void onDeviceDisconnect(QString serial);
|
||||
void onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
|
||||
|
||||
// neend convert frameSize to its frameSize
|
||||
void onMouseEvent(const QMouseEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void onWheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
void onKeyEvent(const QKeyEvent *from, const QSize &frameSize, const QSize &showSize);
|
||||
|
||||
private:
|
||||
quint16 getFreePort();
|
||||
|
||||
private:
|
||||
QMap<QString, QPointer<Device>> m_devices;
|
||||
quint16 m_localPortStart = 27183;
|
||||
QString m_script;
|
||||
};
|
||||
|
||||
#endif // DEVICEMANAGE_H
|
5
QtScrcpy/devicemanage/devicemanage.pri
Normal file
5
QtScrcpy/devicemanage/devicemanage.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/devicemanage.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/devicemanage.cpp
|
424
QtScrcpy/dialog.cpp
Normal file
424
QtScrcpy/dialog.cpp
Normal file
|
@ -0,0 +1,424 @@
|
|||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "config.h"
|
||||
#include "device.h"
|
||||
#include "dialog.h"
|
||||
#include "keymap.h"
|
||||
#include "ui_dialog.h"
|
||||
#include "videoform.h"
|
||||
|
||||
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
initUI();
|
||||
|
||||
connect(&m_adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
QString log = "";
|
||||
bool newLine = true;
|
||||
QStringList args = m_adb.arguments();
|
||||
|
||||
switch (processResult) {
|
||||
case AdbProcess::AER_ERROR_START:
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_START:
|
||||
log = "adb run";
|
||||
newLine = false;
|
||||
break;
|
||||
case AdbProcess::AER_ERROR_EXEC:
|
||||
//log = m_adb.getErrorOut();
|
||||
if (args.contains("ifconfig") && args.contains("wlan0")) {
|
||||
getIPbyIp();
|
||||
}
|
||||
break;
|
||||
case AdbProcess::AER_ERROR_MISSING_BINARY:
|
||||
log = "adb not find";
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_EXEC:
|
||||
//log = m_adb.getStdOut();
|
||||
if (args.contains("devices")) {
|
||||
QStringList devices = m_adb.getDevicesSerialFromStdOut();
|
||||
ui->serialBox->clear();
|
||||
for (auto &item : devices) {
|
||||
ui->serialBox->addItem(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->setText(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->setText(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->setText(ip);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!log.isEmpty()) {
|
||||
outLog(log, newLine);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
m_deviceManage.disconnectAllDevice();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void Dialog::initUI()
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowFlags(windowFlags() | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
|
||||
|
||||
ui->bitRateBox->addItem("2000000");
|
||||
ui->bitRateBox->addItem("6000000");
|
||||
ui->bitRateBox->addItem("8000000");
|
||||
ui->bitRateBox->addItem("10000000");
|
||||
ui->bitRateBox->addItem("20000000");
|
||||
ui->bitRateBox->addItem("50000000");
|
||||
ui->bitRateBox->addItem("100000000");
|
||||
ui->bitRateBox->addItem("200000000");
|
||||
ui->bitRateBox->setCurrentIndex(Config::getInstance().getBitRateIndex());
|
||||
|
||||
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->maxSizeBox->setCurrentIndex(Config::getInstance().getMaxSizeIndex());
|
||||
|
||||
ui->formatBox->addItem("mp4");
|
||||
ui->formatBox->addItem("mkv");
|
||||
ui->formatBox->setCurrentIndex(Config::getInstance().getRecordFormatIndex());
|
||||
|
||||
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);
|
||||
|
||||
ui->recordPathEdt->setText(Config::getInstance().getRecordPath());
|
||||
ui->framelessCheck->setChecked(Config::getInstance().getFramelessWindow());
|
||||
|
||||
#ifdef Q_OS_OSX
|
||||
// mac need more width
|
||||
setFixedWidth(520);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
// linux need more width
|
||||
setFixedWidth(480);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Dialog::execAdbCmd()
|
||||
{
|
||||
if (checkAdbRun()) {
|
||||
return;
|
||||
}
|
||||
QString cmd = ui->adbCommandEdt->text().trimmed();
|
||||
outLog("adb " + cmd, false);
|
||||
m_adb.execute(ui->serialBox->currentText().trimmed(), cmd.split(" ", QString::SkipEmptyParts));
|
||||
}
|
||||
|
||||
QString Dialog::getGameScript(const QString &fileName)
|
||||
{
|
||||
QFile loadFile(KeyMap::getKeyMapPath() + "/" + fileName);
|
||||
if (!loadFile.open(QIODevice::ReadOnly)) {
|
||||
outLog("open file failed:" + fileName, true);
|
||||
return "";
|
||||
}
|
||||
|
||||
QString ret = loadFile.readAll();
|
||||
loadFile.close();
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
QString absFilePath;
|
||||
if (ui->recordScreenCheck->isChecked()) {
|
||||
QString fileDir(ui->recordPathEdt->text().trimmed());
|
||||
if (!fileDir.isEmpty()) {
|
||||
QDateTime dateTime = QDateTime::currentDateTime();
|
||||
QString fileName = dateTime.toString("_yyyyMMdd_hhmmss_zzz");
|
||||
QString ext = ui->formatBox->currentText().trimmed();
|
||||
fileName = windowTitle() + fileName + "." + ext;
|
||||
QDir dir(fileDir);
|
||||
absFilePath = dir.absoluteFilePath(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
quint32 bitRate = ui->bitRateBox->currentText().trimmed().toUInt();
|
||||
// this is ok that "native" toUshort is 0
|
||||
quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();
|
||||
Device::DeviceParams params;
|
||||
params.serial = ui->serialBox->currentText().trimmed();
|
||||
params.maxSize = videoSize;
|
||||
params.bitRate = bitRate;
|
||||
// on devices with Android >= 10, the capture frame rate can be limited
|
||||
params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
|
||||
params.recordFileName = absFilePath;
|
||||
params.closeScreen = ui->closeScreenCheck->isChecked();
|
||||
params.useReverse = ui->useReverseCheck->isChecked();
|
||||
params.display = !ui->notDisplayCheck->isChecked();
|
||||
params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();
|
||||
params.lockVideoOrientation = ui->lockOrientationBox->currentIndex() - 1;
|
||||
params.stayAwake = ui->stayAwakeCheck->isChecked();
|
||||
|
||||
m_deviceManage.connectDevice(params);
|
||||
|
||||
if (ui->alwaysTopCheck->isChecked()) {
|
||||
m_deviceManage.staysOnTop(params.serial);
|
||||
}
|
||||
m_deviceManage.showFPS(params.serial, ui->fpsCheck->isChecked());
|
||||
}
|
||||
|
||||
void Dialog::on_stopServerBtn_clicked()
|
||||
{
|
||||
if (m_deviceManage.disconnectDevice(ui->serialBox->currentText().trimmed())) {
|
||||
outLog("stop server");
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::on_wirelessConnectBtn_clicked()
|
||||
{
|
||||
if (checkAdbRun()) {
|
||||
return;
|
||||
}
|
||||
QString addr = ui->deviceIpEdt->text().trimmed();
|
||||
if (!ui->devicePortEdt->text().isEmpty()) {
|
||||
addr += ":";
|
||||
addr += ui->devicePortEdt->text().trimmed();
|
||||
} else if (!ui->devicePortEdt->placeholderText().isEmpty()) {
|
||||
addr += ":";
|
||||
addr += ui->devicePortEdt->placeholderText().trimmed();
|
||||
} else {
|
||||
outLog("error: device port is null", false);
|
||||
return;
|
||||
}
|
||||
|
||||
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::on_wirelessDisConnectBtn_clicked()
|
||||
{
|
||||
if (checkAdbRun()) {
|
||||
return;
|
||||
}
|
||||
QString addr = ui->deviceIpEdt->text().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)
|
||||
{
|
||||
Config::getInstance().setRecordPath(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()
|
||||
{
|
||||
m_deviceManage.disconnectAllDevice();
|
||||
}
|
||||
|
||||
void Dialog::on_refreshGameScriptBtn_clicked()
|
||||
{
|
||||
ui->gameBox->clear();
|
||||
QDir dir(KeyMap::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()
|
||||
{
|
||||
m_deviceManage.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_bitRateBox_activated(int index)
|
||||
{
|
||||
Config::getInstance().setBitRateIndex(index);
|
||||
}
|
||||
|
||||
void Dialog::on_maxSizeBox_activated(int index)
|
||||
{
|
||||
Config::getInstance().setMaxSizeIndex(index);
|
||||
}
|
||||
|
||||
void Dialog::on_formatBox_activated(int index)
|
||||
{
|
||||
Config::getInstance().setRecordFormatIndex(index);
|
||||
}
|
||||
|
||||
void Dialog::on_framelessCheck_stateChanged(int arg1)
|
||||
{
|
||||
Q_UNUSED(arg1)
|
||||
Config::getInstance().setFramelessWindow(ui->framelessCheck->isChecked());
|
||||
}
|
81
QtScrcpy/dialog.h
Normal file
81
QtScrcpy/dialog.h
Normal file
|
@ -0,0 +1,81 @@
|
|||
#ifndef DIALOG_H
|
||||
#define DIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPointer>
|
||||
|
||||
#include "adbprocess.h"
|
||||
#include "devicemanage.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class Dialog;
|
||||
}
|
||||
|
||||
class QYUVOpenGLWidget;
|
||||
class Dialog : public QDialog
|
||||
{
|
||||
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 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_bitRateBox_activated(int index);
|
||||
|
||||
void on_maxSizeBox_activated(int index);
|
||||
|
||||
void on_formatBox_activated(int index);
|
||||
|
||||
void on_framelessCheck_stateChanged(int arg1);
|
||||
|
||||
private:
|
||||
bool checkAdbRun();
|
||||
void initUI();
|
||||
void execAdbCmd();
|
||||
QString getGameScript(const QString &fileName);
|
||||
|
||||
private:
|
||||
Ui::Dialog *ui;
|
||||
AdbProcess m_adb;
|
||||
DeviceManage m_deviceManage;
|
||||
};
|
||||
|
||||
#endif // DIALOG_H
|
701
QtScrcpy/dialog.ui
Normal file
701
QtScrcpy/dialog.ui
Normal file
|
@ -0,0 +1,701 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>420</width>
|
||||
<height>517</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>420</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>420</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">QtScrcpy</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="configGroupBox">
|
||||
<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">
|
||||
<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="text">
|
||||
<string>bit rate:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="bitRateBox">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>max size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="maxSizeBox">
|
||||
<property name="toolTip">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>record format:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="formatBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="configWidget5" native="true">
|
||||
<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_8">
|
||||
<property name="text">
|
||||
<string>lock orientation:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="lockOrientationBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="configWidget2" native="true">
|
||||
<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="text">
|
||||
<string>record save path:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>recordPathEdt</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="recordPathEdt">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="selectRecordPathBtn">
|
||||
<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">
|
||||
<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">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="refreshGameScriptBtn">
|
||||
<property name="text">
|
||||
<string>refresh script</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="applyScriptBtn">
|
||||
<property name="text">
|
||||
<string>apply</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="configWidget3" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<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 row="1" column="1">
|
||||
<widget class="QCheckBox" name="closeScreenCheck">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>screen-off</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QCheckBox" name="framelessCheck">
|
||||
<property name="text">
|
||||
<string>frameless</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="alwaysTopCheck">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>always on top</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="notDisplayCheck">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>background record</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QCheckBox" name="useReverseCheck">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>reverse connection</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="recordScreenCheck">
|
||||
<property name="text">
|
||||
<string>record screen</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QCheckBox" name="fpsCheck">
|
||||
<property name="text">
|
||||
<string>show fps</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QCheckBox" name="stayAwakeCheck">
|
||||
<property name="text">
|
||||
<string>stay awake</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="usbGroupBox">
|
||||
<property name="title">
|
||||
<string>USB line</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="usbWidget1" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<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_2">
|
||||
<property name="text">
|
||||
<string>device serial:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="serialBox"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="startServerBtn">
|
||||
<property name="text">
|
||||
<string>start server</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="stopServerBtn">
|
||||
<property name="text">
|
||||
<string>stop server</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="usbWidget2" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<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="QPushButton" name="stopAllServerBtn">
|
||||
<property name="text">
|
||||
<string>stop all server</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="updateDevice">
|
||||
<property name="text">
|
||||
<string>refresh devices</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="getIPBtn">
|
||||
<property name="text">
|
||||
<string>get device IP</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="startAdbdBtn">
|
||||
<property name="text">
|
||||
<string>start adbd</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="wirelessGroupBox">
|
||||
<property name="title">
|
||||
<string>Wireless</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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="QLineEdit" name="deviceIpEdt">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>128</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string notr="true">192.168.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="devicePortEdt">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>60</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="maxLength">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string notr="true">5555</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="wirelessConnectBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>wireless connect</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="wirelessDisConnectBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>wireless disconnect</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="adbGroupBox">
|
||||
<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="text">
|
||||
<string>adb command:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>adbCommandEdt</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="adbCommandEdt">
|
||||
<property name="text">
|
||||
<string notr="true">devices</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="adbCommandBtn">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<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="Minimum" vsizetype="Fixed">
|
||||
<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="Minimum" vsizetype="Fixed">
|
||||
<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="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>140</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::ClickFocus</enum>
|
||||
</property>
|
||||
<property name="documentTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<tabstops>
|
||||
<tabstop>deviceIpEdt</tabstop>
|
||||
<tabstop>devicePortEdt</tabstop>
|
||||
<tabstop>wirelessConnectBtn</tabstop>
|
||||
<tabstop>wirelessDisConnectBtn</tabstop>
|
||||
<tabstop>adbCommandEdt</tabstop>
|
||||
<tabstop>adbCommandBtn</tabstop>
|
||||
<tabstop>stopAdbBtn</tabstop>
|
||||
<tabstop>clearOut</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
5
QtScrcpy/fontawesome/fontawesome.pri
Normal file
5
QtScrcpy/fontawesome/fontawesome.pri
Normal file
|
@ -0,0 +1,5 @@
|
|||
HEADERS += \
|
||||
$$PWD/iconhelper.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/iconhelper.cpp
|
|
@ -1,443 +0,0 @@
|
|||
#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);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
#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
|
|
@ -1,4 +1,4 @@
|
|||
#include <QApplication>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QSurfaceFormat>
|
||||
|
@ -9,8 +9,10 @@
|
|||
#include "config.h"
|
||||
#include "dialog.h"
|
||||
#include "mousetap/mousetap.h"
|
||||
#include "stream.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();
|
||||
|
@ -22,30 +24,26 @@ 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");
|
||||
qputenv("QTSCRCPY_ADB_PATH", "../../../../third_party/adb/win/adb.exe");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../../../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
|
||||
qputenv("QTSCRCPY_ADB_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/adb/linux/adb");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
|
||||
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
|
||||
qputenv("QTSCRCPY_ADB_PATH", "../../../third_party/adb/linux/adb");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server");
|
||||
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
|
||||
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
|
||||
#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);
|
||||
|
@ -55,14 +53,8 @@ int main(int argc, char *argv[])
|
|||
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);
|
||||
|
@ -77,6 +69,7 @@ int main(int argc, char *argv[])
|
|||
QSurfaceFormat::setDefaultFormat(varFormat);
|
||||
|
||||
g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);
|
||||
Stream::init();
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// windows下通过qmake VERSION变量或者rc设置版本号和应用名称后,这里可以直接拿到
|
||||
|
@ -96,7 +89,7 @@ int main(int argc, char *argv[])
|
|||
MouseTap::getInstance()->initMouseEventTap();
|
||||
#endif
|
||||
|
||||
// load style sheet
|
||||
//加载样式表
|
||||
QFile file(":/qss/psblack.css");
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
QString qss = QLatin1String(file.readAll());
|
||||
|
@ -106,29 +99,25 @@ int main(int argc, char *argv[])
|
|||
file.close();
|
||||
}
|
||||
|
||||
qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());
|
||||
|
||||
g_mainDlg = new Dialog {};
|
||||
g_mainDlg = new Dialog;
|
||||
g_mainDlg->setWindowTitle(Config::getInstance().getTitle());
|
||||
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("You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1>");
|
||||
qInfo(
|
||||
"%s",
|
||||
QObject::tr("This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the "
|
||||
"following address:")
|
||||
.toUtf8()
|
||||
.data());
|
||||
qInfo() << QString("QtScrcpy %1 <https://github.com/barry-ran/QtScrcpy>").arg(QCoreApplication::applicationVersion()).toUtf8();
|
||||
|
||||
int ret = a.exec();
|
||||
delete g_mainDlg;
|
||||
|
||||
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
|
||||
MouseTap::getInstance()->quitMouseEventTap();
|
||||
#endif
|
||||
|
||||
Stream::deInit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -137,28 +126,18 @@ 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;
|
||||
}
|
||||
|
||||
//language = QLocale::English;
|
||||
QString languagePath = ":/i18n/";
|
||||
switch (language) {
|
||||
case QLocale::Chinese:
|
||||
languagePath += "zh_CN.qm";
|
||||
languagePath += "QtScrcpy_zh.qm";
|
||||
break;
|
||||
case QLocale::English:
|
||||
default:
|
||||
languagePath += "en_US.qm";
|
||||
break;
|
||||
languagePath += "QtScrcpy_en.qm";
|
||||
}
|
||||
|
||||
auto loaded = translator.load(languagePath);
|
||||
if (!loaded) {
|
||||
qWarning() << "Failed to load translation file:" << languagePath;
|
||||
}
|
||||
translator.load(languagePath);
|
||||
qApp->installTranslator(&translator);
|
||||
}
|
||||
|
||||
|
@ -193,12 +172,12 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
|||
g_oldMessageHandler(type, context, msg);
|
||||
}
|
||||
|
||||
// Is Qt log level higher than warning?
|
||||
float fLogLevel = g_msgType;
|
||||
// qt log info big than warning?
|
||||
float fLogLevel = 1.0f * g_msgType;
|
||||
if (QtInfoMsg == g_msgType) {
|
||||
fLogLevel = QtDebugMsg + 0.5f;
|
||||
}
|
||||
float fLogLevel2 = type;
|
||||
float fLogLevel2 = 1.0f * type;
|
||||
if (QtInfoMsg == type) {
|
||||
fLogLevel2 = QtDebugMsg + 0.5f;
|
||||
}
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>zh-Hans</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>QtScrcpy</string>
|
||||
<string>@EXECUTABLE@</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>Created by rankun</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>QtScrcpy</string>
|
||||
<string>@ICON@</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>rankun.QtScrcpy</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
|
@ -19,13 +19,13 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${BUNDLE_VERSION}</string>
|
||||
<string>1.0.0</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>MacOSX</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${BUNDLE_VERSION}</string>
|
||||
<string>1.0.0</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>10.10</string>
|
||||
<key>NSAppleEventsUsageDescription</key>
|
|
@ -1,51 +0,0 @@
|
|||
# 声明ts文件
|
||||
set(QC_TS_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/zh_CN.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/en_US.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
|
BIN
QtScrcpy/res/i18n/QtScrcpy_en.qm
Normal file
BIN
QtScrcpy/res/i18n/QtScrcpy_en.qm
Normal file
Binary file not shown.
|
@ -1,321 +1,352 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>Device</name>
|
||||
<message>
|
||||
<source>wait current file transfer to complete</source>
|
||||
<translation type="vanished">wait current file transfer to complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer complete</source>
|
||||
<translation type="vanished">file transfer complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer failed</source>
|
||||
<translation type="vanished">file transfer failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="181"/>
|
||||
<source>install apk</source>
|
||||
<translation>install apk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="183"/>
|
||||
<source>file transfer</source>
|
||||
<translation>file transfer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="187"/>
|
||||
<source>wait current %1 to complete</source>
|
||||
<translation>wait current %1 to complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="190"/>
|
||||
<source>%1 complete, save in %2</source>
|
||||
<translation>%1 complete, save in %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 complete
|
||||
save in %2</source>
|
||||
<translation type="vanished">%1 complete\n save in %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="193"/>
|
||||
<source>%1 failed</source>
|
||||
<translation>%1 failed</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Dialog</name>
|
||||
<message>
|
||||
<source>show</source>
|
||||
<translation>show</translation>
|
||||
<location filename="../../dialog.ui" line="477"/>
|
||||
<source>Wireless</source>
|
||||
<translation>Wireless</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>quit</source>
|
||||
<translation>quit</translation>
|
||||
<location filename="../../dialog.ui" line="561"/>
|
||||
<source>wireless connect</source>
|
||||
<translation>wireless connect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>original</source>
|
||||
<translation>original</translation>
|
||||
<location filename="../../dialog.ui" line="577"/>
|
||||
<source>wireless disconnect</source>
|
||||
<translation>wireless disconnect</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no lock</source>
|
||||
<translation>no lock</translation>
|
||||
<location filename="../../dialog.ui" line="32"/>
|
||||
<source>Start Config</source>
|
||||
<translation>Start Config</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Notice</source>
|
||||
<translation>Notice</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hidden here!</source>
|
||||
<translation>Hidden here!</translation>
|
||||
<location filename="../../dialog.ui" line="168"/>
|
||||
<source>record save path:</source>
|
||||
<translation>record save path:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="185"/>
|
||||
<location filename="../../dialog.cpp" line="338"/>
|
||||
<source>select path</source>
|
||||
<translation>select path</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear History</source>
|
||||
<translation>Clear History</translation>
|
||||
<location filename="../../dialog.ui" line="99"/>
|
||||
<source>record format:</source>
|
||||
<translation>record format:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="323"/>
|
||||
<source>record screen</source>
|
||||
<translation>record screen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="268"/>
|
||||
<source>frameless</source>
|
||||
<translation>frameless</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="127"/>
|
||||
<source>lock orientation:</source>
|
||||
<translation>lock orientation:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="330"/>
|
||||
<source>show fps</source>
|
||||
<translation>show fps</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="337"/>
|
||||
<source>stay awake</source>
|
||||
<translation>stay awake</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="434"/>
|
||||
<source>stop all server</source>
|
||||
<translation>stop all server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="611"/>
|
||||
<source>adb command:</source>
|
||||
<translation>adb command:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="647"/>
|
||||
<source>terminate</source>
|
||||
<translation>terminate</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="634"/>
|
||||
<source>execute</source>
|
||||
<translation>execute</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="660"/>
|
||||
<source>clear</source>
|
||||
<translation>clear</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="313"/>
|
||||
<source>reverse connection</source>
|
||||
<translation>reverse connection</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>auto enable</source>
|
||||
<translation type="vanished">auto enable</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="297"/>
|
||||
<source>background record</source>
|
||||
<translation>background record</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="261"/>
|
||||
<source>screen-off</source>
|
||||
<translation>screen-off</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="230"/>
|
||||
<source>apply</source>
|
||||
<translation>apply</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="85"/>
|
||||
<source>max size:</source>
|
||||
<translation>max size:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="281"/>
|
||||
<source>always on top</source>
|
||||
<translation>always on top</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="223"/>
|
||||
<source>refresh script</source>
|
||||
<translation>refresh script</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="451"/>
|
||||
<source>get device IP</source>
|
||||
<translation>get device IP</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="350"/>
|
||||
<source>USB line</source>
|
||||
<translation>USB line</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="406"/>
|
||||
<source>stop server</source>
|
||||
<translation>stop server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="396"/>
|
||||
<source>start server</source>
|
||||
<translation>start server</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="386"/>
|
||||
<source>device serial:</source>
|
||||
<translation>device serial:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Config</source>
|
||||
<translation type="vanished">Config</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="68"/>
|
||||
<source>bit rate:</source>
|
||||
<translation>bit rate:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="461"/>
|
||||
<source>start adbd</source>
|
||||
<translation>start adbd</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="441"/>
|
||||
<source>refresh devices</source>
|
||||
<translation>refresh devices</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.cpp" line="105"/>
|
||||
<source>original</source>
|
||||
<translation>original</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.cpp" line="112"/>
|
||||
<source>no lock</source>
|
||||
<translation>no lock</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>
|
||||
<source>This software is completely open source and free, you can download it at the following address:</source>
|
||||
<translation type="vanished">This software is completely open source and free, you can download it at the following address:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>QuickMirror</source>
|
||||
<translation>QuickMirror</translation>
|
||||
<source>This software is completely open source and free.
|
||||
Strictly used for illegal purposes, or at your own risk.
|
||||
You can download it at the following address:</source>
|
||||
<translation type="vanished">This software is completely open source and free.\nStrictly used for illegal purposes, or at your own risk.\nYou can download it at the following address:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If you need more professional batch control mirror software, you can try the following software:</source>
|
||||
<translation>If you need more professional batch control mirror software, you can try the following software:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
|
||||
<translation>If you need more professional game keymap mirror software, you can try the following software:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>QuickAssistant</source>
|
||||
<translation>QuickAssistant</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
|
||||
<translation>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></translation>
|
||||
<location filename="../../main.cpp" line="108"/>
|
||||
<source>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</source>
|
||||
<translation>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ToolForm</name>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="14"/>
|
||||
<source>Tool</source>
|
||||
<translation>Tool</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="33"/>
|
||||
<source>full screen</source>
|
||||
<translation>full screen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="56"/>
|
||||
<source>expand notify</source>
|
||||
<translation>expand notify</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>turn off</source>
|
||||
<translation type="vanished">turn off</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>turn on</source>
|
||||
<translation type="vanished">turn on</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="66"/>
|
||||
<source>touch switch</source>
|
||||
<translation>touch switch</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="76"/>
|
||||
<source>close screen</source>
|
||||
<translation>close screen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="86"/>
|
||||
<source>power</source>
|
||||
<translation>power</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="96"/>
|
||||
<source>volume up</source>
|
||||
<translation>volume up</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="106"/>
|
||||
<source>volume down</source>
|
||||
<translation>volume down</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="116"/>
|
||||
<source>app switch</source>
|
||||
<translation>app switch</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="126"/>
|
||||
<source>menu</source>
|
||||
<translation>menu</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="136"/>
|
||||
<source>home</source>
|
||||
<translation>home</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="146"/>
|
||||
<source>return</source>
|
||||
<translation>return</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="156"/>
|
||||
<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>wait current file transfer to complete</source>
|
||||
<translation type="vanished">wait current file transfer to complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer complete</source>
|
||||
<translation type="vanished">file transfer complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer failed</source>
|
||||
<translation type="vanished">file transfer failed</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/videoform.cpp" line="710"/>
|
||||
<source>file does not exist</source>
|
||||
<translation>file does not exist</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Widget</name>
|
||||
<name>videoForm</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>
|
||||
<source>qrc:/qml/pinwheel.qml</source>
|
||||
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
BIN
QtScrcpy/res/i18n/QtScrcpy_zh.qm
Normal file
BIN
QtScrcpy/res/i18n/QtScrcpy_zh.qm
Normal file
Binary file not shown.
|
@ -1,321 +1,352 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="zh_CN">
|
||||
<context>
|
||||
<name>Device</name>
|
||||
<message>
|
||||
<source>wait current file transfer to complete</source>
|
||||
<translation type="vanished">等待当前文件传输完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer complete</source>
|
||||
<translation type="vanished">文件传输完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer failed</source>
|
||||
<translation type="vanished">文件传输失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="181"/>
|
||||
<source>install apk</source>
|
||||
<translation>安装apk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="183"/>
|
||||
<source>file transfer</source>
|
||||
<translation>文件传输</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="187"/>
|
||||
<source>wait current %1 to complete</source>
|
||||
<translation>等待当前%1完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="190"/>
|
||||
<source>%1 complete, save in %2</source>
|
||||
<translation>%1完成,保存在%2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>%1 complete
|
||||
save in %2</source>
|
||||
<translation type="vanished">%1完成\n 保存在 %2</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/device.cpp" line="193"/>
|
||||
<source>%1 failed</source>
|
||||
<translation>%1 失败</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Dialog</name>
|
||||
<message>
|
||||
<source>show</source>
|
||||
<translation>显示</translation>
|
||||
<location filename="../../dialog.ui" line="477"/>
|
||||
<source>Wireless</source>
|
||||
<translation>无线</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>quit</source>
|
||||
<translation>退出</translation>
|
||||
<location filename="../../dialog.ui" line="561"/>
|
||||
<source>wireless connect</source>
|
||||
<translation>无线连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>original</source>
|
||||
<translation>原始</translation>
|
||||
<location filename="../../dialog.ui" line="577"/>
|
||||
<source>wireless disconnect</source>
|
||||
<translation>无线断开</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>no lock</source>
|
||||
<translation>不锁定</translation>
|
||||
<location filename="../../dialog.ui" line="32"/>
|
||||
<source>Start Config</source>
|
||||
<translation>启动配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Notice</source>
|
||||
<translation>提示</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hidden here!</source>
|
||||
<translation>安卓录屏程序隐藏在这!</translation>
|
||||
<location filename="../../dialog.ui" line="168"/>
|
||||
<source>record save path:</source>
|
||||
<translation>录像保存路径:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="185"/>
|
||||
<location filename="../../dialog.cpp" line="338"/>
|
||||
<source>select path</source>
|
||||
<translation>选择路径</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear History</source>
|
||||
<translation>清理历史</translation>
|
||||
<location filename="../../dialog.ui" line="99"/>
|
||||
<source>record format:</source>
|
||||
<translation>录制格式:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="323"/>
|
||||
<source>record screen</source>
|
||||
<translation>录制屏幕</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="268"/>
|
||||
<source>frameless</source>
|
||||
<translation>无边框</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="127"/>
|
||||
<source>lock orientation:</source>
|
||||
<translation>锁定方向:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="330"/>
|
||||
<source>show fps</source>
|
||||
<translation>显示fps</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="337"/>
|
||||
<source>stay awake</source>
|
||||
<translation>保持唤醒</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="434"/>
|
||||
<source>stop all server</source>
|
||||
<translation>停止所有服务</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="611"/>
|
||||
<source>adb command:</source>
|
||||
<translation>adb命令:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="647"/>
|
||||
<source>terminate</source>
|
||||
<translation>终止</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="634"/>
|
||||
<source>execute</source>
|
||||
<translation>执行</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="660"/>
|
||||
<source>clear</source>
|
||||
<translation>清理</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="313"/>
|
||||
<source>reverse connection</source>
|
||||
<translation>反向连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>auto enable</source>
|
||||
<translation type="vanished">自动启用脚本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="297"/>
|
||||
<source>background record</source>
|
||||
<translation>后台录制</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="261"/>
|
||||
<source>screen-off</source>
|
||||
<translation>自动息屏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="230"/>
|
||||
<source>apply</source>
|
||||
<translation>应用脚本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="85"/>
|
||||
<source>max size:</source>
|
||||
<translation>最大尺寸:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="281"/>
|
||||
<source>always on top</source>
|
||||
<translation>窗口置顶</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="223"/>
|
||||
<source>refresh script</source>
|
||||
<translation>刷新脚本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="451"/>
|
||||
<source>get device IP</source>
|
||||
<translation>获取设备IP</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="350"/>
|
||||
<source>USB line</source>
|
||||
<translation>USB线</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="406"/>
|
||||
<source>stop server</source>
|
||||
<translation>停止服务</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="396"/>
|
||||
<source>start server</source>
|
||||
<translation>启动服务</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="386"/>
|
||||
<source>device serial:</source>
|
||||
<translation>设备序列号:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Config</source>
|
||||
<translation type="vanished">配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="68"/>
|
||||
<source>bit rate:</source>
|
||||
<translation>比特率:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="461"/>
|
||||
<source>start adbd</source>
|
||||
<translation>启动adbd</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.ui" line="441"/>
|
||||
<source>refresh devices</source>
|
||||
<translation>刷新设备列表</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.cpp" line="105"/>
|
||||
<source>original</source>
|
||||
<translation>原始</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../dialog.cpp" line="112"/>
|
||||
<source>no lock</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>
|
||||
<source>This software is completely open source and free, you can download it at the following address:</source>
|
||||
<translation type="vanished">本软件完全开源免费,你可以在下面的地址下载:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>QuickMirror</source>
|
||||
<translation>极限投屏</translation>
|
||||
<source>This software is completely open source and free.
|
||||
Strictly used for illegal purposes, or at your own risk.
|
||||
You can download it at the following address:</source>
|
||||
<translation type="vanished">本软件完全开源免费.\n严禁用于非法用途,否则后果自负.\n你可以在下面地址下载:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If you need more professional batch control mirror software, you can try the following software:</source>
|
||||
<translation>如果你需要更专业的批量控制投屏软件,你可以尝试下面软件:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>If you need more professional game keymap mirror software, you can try the following software:</source>
|
||||
<translation>如果你需要更专业的游戏映射投屏软件,你可以尝试下面软件:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>QuickAssistant</source>
|
||||
<translation>极限手游助手</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>You can contact me with telegram <https://t.me/+Ylf_5V_rDCMyODQ1></source>
|
||||
<translation>你可以通过QQ群联系我 <901736468></translation>
|
||||
<location filename="../../main.cpp" line="108"/>
|
||||
<source>This software is completely open source and free. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</source>
|
||||
<translation>本软件完全开源免费,严禁用于非法用途,否则后果自负,你可以在下面地址下载:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>ToolForm</name>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="14"/>
|
||||
<source>Tool</source>
|
||||
<translation>工具</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="33"/>
|
||||
<source>full screen</source>
|
||||
<translation>全屏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="56"/>
|
||||
<source>expand notify</source>
|
||||
<translation>下拉通知</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>turn off</source>
|
||||
<translation type="vanished">锁屏</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>turn on</source>
|
||||
<translation type="vanished">解锁</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="66"/>
|
||||
<source>touch switch</source>
|
||||
<translation>触摸显示开关</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="76"/>
|
||||
<source>close screen</source>
|
||||
<translation>关闭屏幕</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="86"/>
|
||||
<source>power</source>
|
||||
<translation>电源</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="96"/>
|
||||
<source>volume up</source>
|
||||
<translation>音量加</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="106"/>
|
||||
<source>volume down</source>
|
||||
<translation>音量减</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="116"/>
|
||||
<source>app switch</source>
|
||||
<translation>切换应用</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="126"/>
|
||||
<source>menu</source>
|
||||
<translation>菜单</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="136"/>
|
||||
<source>home</source>
|
||||
<translation>主界面</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="146"/>
|
||||
<source>return</source>
|
||||
<translation>返回</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/toolform.ui" line="156"/>
|
||||
<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>wait current file transfer to complete</source>
|
||||
<translation type="vanished">等待当前文件传输完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer complete</source>
|
||||
<translation type="vanished">文件传输完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer failed</source>
|
||||
<translation type="vanished">文件传输失败</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../device/ui/videoform.cpp" line="710"/>
|
||||
<source>file does not exist</source>
|
||||
<translation>文件不存在</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Widget</name>
|
||||
<name>videoForm</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>
|
||||
<source>qrc:/qml/pinwheel.qml</source>
|
||||
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 12 KiB |
|
@ -2,7 +2,7 @@
|
|||
<qresource prefix="/">
|
||||
<file>font/fontawesome-webfont.ttf</file>
|
||||
<file>image/videoform/phone-h.png</file>
|
||||
<file>image/videoform/phone-v.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>
|
||||
|
@ -22,8 +22,7 @@
|
|||
<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>image/tray/logo.png</file>
|
||||
<file>i18n/QtScrcpy_en.qm</file>
|
||||
<file>i18n/QtScrcpy_zh.qm</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
Binary file not shown.
|
@ -1,53 +0,0 @@
|
|||
@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%
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue