mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-04-20 19:44:59 +00:00
Merge pull request #621 from barry-ran/dev
refactor: add QtScrcpyCore submodule
This commit is contained in:
commit
d5e915d404
208 changed files with 971 additions and 37073 deletions
6
.github/workflows/macos.yml
vendored
6
.github/workflows/macos.yml
vendored
|
@ -35,9 +35,11 @@ jobs:
|
|||
with:
|
||||
version: ${{ matrix.qt-ver }}
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: 0
|
||||
submodules: 'true'
|
||||
ssh-key: ${{ secrets.BOT_SSH_KEY }}
|
||||
# 编译
|
||||
- name: Build MacOS
|
||||
env:
|
||||
|
|
6
.github/workflows/ubuntu.yml
vendored
6
.github/workflows/ubuntu.yml
vendored
|
@ -40,9 +40,11 @@ jobs:
|
|||
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@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: 0
|
||||
submodules: 'true'
|
||||
ssh-key: ${{ secrets.BOT_SSH_KEY }}
|
||||
- name: Build Ubuntu
|
||||
env:
|
||||
ENV_QT_PATH: ${{ env.qt-install-path }}
|
||||
|
|
6
.github/workflows/windows.yml
vendored
6
.github/workflows/windows.yml
vendored
|
@ -64,9 +64,11 @@ jobs:
|
|||
arch: ${{ matrix.qt-arch }}
|
||||
cached: ${{ steps.cache-qt.outputs.cache-hit }}
|
||||
# 拉取代码
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
fetch-depth: 0
|
||||
submodules: 'true'
|
||||
ssh-key: ${{ secrets.BOT_SSH_KEY }}
|
||||
# 编译msvc
|
||||
- name: Build MSVC
|
||||
# shell介绍 https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell
|
||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "QtScrcpy/QtScrcpyCore"]
|
||||
path = QtScrcpy/QtScrcpyCore
|
||||
url = git@github.com:barry-ran/QtScrcpyCore.git
|
|
@ -29,7 +29,7 @@ endif()
|
|||
message(STATUS "[${PROJECT_NAME}] CPU_ARCH:${QC_CPU_ARCH}")
|
||||
|
||||
# CMake set
|
||||
#set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
@ -68,7 +68,7 @@ if (NOT MSVC)
|
|||
add_compile_options(-Wall -Wextra -pedantic -Werror)
|
||||
|
||||
# disable some warning
|
||||
add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions)
|
||||
add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
|
||||
endif()
|
||||
|
||||
#
|
||||
|
@ -79,8 +79,8 @@ set(CMAKE_AUTOUIC ON)
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network LinguistTools REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network LinguistTools REQUIRED)
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network REQUIRED)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS X11Extras REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS X11Extras REQUIRED)
|
||||
|
@ -92,79 +92,6 @@ message(STATUS "[${PROJECT_NAME}] Qt version is: ${QT_VERSION_MAJOR}.${QT_VERSIO
|
|||
# Sources
|
||||
#
|
||||
|
||||
# adb
|
||||
set(QC_ADB_SOURCES
|
||||
adb/adbprocess.h
|
||||
adb/adbprocess.cpp
|
||||
)
|
||||
source_group(adb FILES ${QC_ADB_SOURCES})
|
||||
|
||||
# common
|
||||
set(QC_COMMON_SOURCES
|
||||
common/qscrcpyevent.h
|
||||
)
|
||||
source_group(common FILES ${QC_COMMON_SOURCES})
|
||||
|
||||
# device
|
||||
set(QC_DEVICE_SOURCES
|
||||
device/device.h
|
||||
device/device.cpp
|
||||
device/android/input.h
|
||||
device/android/keycodes.h
|
||||
device/controller/controller.h
|
||||
device/controller/controller.cpp
|
||||
device/controller/inputconvert/inputconvertbase.h
|
||||
device/controller/inputconvert/inputconvertbase.cpp
|
||||
device/controller/inputconvert/inputconvertnormal.h
|
||||
device/controller/inputconvert/inputconvertnormal.cpp
|
||||
device/controller/inputconvert/inputconvertgame.h
|
||||
device/controller/inputconvert/inputconvertgame.cpp
|
||||
device/controller/inputconvert/controlmsg.h
|
||||
device/controller/inputconvert/controlmsg.cpp
|
||||
device/controller/inputconvert/keymap/keymap.h
|
||||
device/controller/inputconvert/keymap/keymap.cpp
|
||||
device/controller/receiver/devicemsg.h
|
||||
device/controller/receiver/devicemsg.cpp
|
||||
device/controller/receiver/receiver.h
|
||||
device/controller/receiver/receiver.cpp
|
||||
device/decoder/avframeconvert.h
|
||||
device/decoder/avframeconvert.cpp
|
||||
device/decoder/decoder.h
|
||||
device/decoder/decoder.cpp
|
||||
device/decoder/fpscounter.h
|
||||
device/decoder/fpscounter.cpp
|
||||
device/decoder/videobuffer.h
|
||||
device/decoder/videobuffer.cpp
|
||||
device/filehandler/filehandler.h
|
||||
device/filehandler/filehandler.cpp
|
||||
device/recorder/recorder.h
|
||||
device/recorder/recorder.cpp
|
||||
device/render/qyuvopenglwidget.h
|
||||
device/render/qyuvopenglwidget.cpp
|
||||
device/server/server.h
|
||||
device/server/server.cpp
|
||||
device/server/tcpserver.h
|
||||
device/server/tcpserver.cpp
|
||||
device/server/videosocket.h
|
||||
device/server/videosocket.cpp
|
||||
device/stream/stream.h
|
||||
device/stream/stream.cpp
|
||||
device/ui/toolform.h
|
||||
device/ui/toolform.cpp
|
||||
device/ui/toolform.ui
|
||||
device/ui/videoform.h
|
||||
device/ui/videoform.cpp
|
||||
device/ui/videoform.ui
|
||||
)
|
||||
source_group(device FILES ${QC_DEVICE_SOURCES})
|
||||
|
||||
# devicemanage
|
||||
set(QC_DEVICEMANAGE_SOURCES
|
||||
devicemanage/devicemanage.h
|
||||
devicemanage/devicemanage.cpp
|
||||
)
|
||||
source_group(devicemanage FILES ${QC_DEVICEMANAGE_SOURCES})
|
||||
|
||||
# fontawesome
|
||||
set(QC_FONTAWESOME_SOURCES
|
||||
fontawesome/iconhelper.h
|
||||
|
@ -181,13 +108,33 @@ set(QC_UIBASE_SOURCES
|
|||
)
|
||||
source_group(uibase FILES ${QC_UIBASE_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/compat.h
|
||||
util/config.h
|
||||
util/config.cpp
|
||||
util/bufferutil.h
|
||||
util/bufferutil.cpp
|
||||
util/mousetap/mousetap.h
|
||||
util/mousetap/mousetap.cpp
|
||||
)
|
||||
|
@ -217,9 +164,6 @@ set(QC_QRC_SOURCES "res/res.qrc")
|
|||
# main
|
||||
set(QC_MAIN_SOURCES
|
||||
main.cpp
|
||||
dialog.cpp
|
||||
dialog.h
|
||||
dialog.ui
|
||||
${QC_QRC_SOURCES}
|
||||
)
|
||||
|
||||
|
@ -243,25 +187,17 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|||
)
|
||||
endif()
|
||||
|
||||
# 使用qt5_add_translation 根据已有ts文件生成qm文件,不用qt5_create_translation
|
||||
# 感兴趣可以了解下qt5_create_translation用法 https://www.cnblogs.com/apocelipes/p/14355460.html
|
||||
set(QC_TS_FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res/i18n/zh_CN.ts
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/res/i18n/en_US.ts
|
||||
)
|
||||
set_source_files_properties(${QC_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/res/i18n")
|
||||
qt5_add_translation(QC_QM_FILES ${QC_TS_FILES})
|
||||
# 翻译相关(使用shell脚本替代cmake处理翻译)
|
||||
# add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/res/i18n)
|
||||
|
||||
# all sources
|
||||
set(QC_PROJECT_SOURCES
|
||||
${QC_ADB_SOURCES}
|
||||
${QC_COMMON_SOURCES}
|
||||
${QC_DEVICE_SOURCES}
|
||||
${QC_DEVICEMANAGE_SOURCES}
|
||||
${QC_FONTAWESOME_SOURCES}
|
||||
${QC_UIBASE_SOURCES}
|
||||
${QC_UI_SOURCES}
|
||||
${QC_UTIL_SOURCES}
|
||||
${QC_MAIN_SOURCES}
|
||||
${QC_GROUP_CONTROLLER}
|
||||
${QC_PLANTFORM_SOURCES}
|
||||
)
|
||||
|
||||
|
@ -279,35 +215,11 @@ 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 adb)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE common)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/filehandler)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/android)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/decoder)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/controller)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/controller/receiver)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/controller/inputconvert)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/controller/inputconvert/keymap)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/server)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/stream)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/render)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/ui)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE device/recorder)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE devicemanage)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE fontawesome)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE util)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE uibase)
|
||||
|
||||
#
|
||||
# common deps
|
||||
#
|
||||
|
||||
# Qt
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
)
|
||||
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
|
||||
|
@ -328,60 +240,21 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
|||
|
||||
# windows
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
# ffmpeg
|
||||
# include
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ../third_party/ffmpeg/include)
|
||||
# link
|
||||
set(FFMPEG_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/${QC_CPU_ARCH}")
|
||||
target_link_directories(${PROJECT_NAME} PRIVATE ${FFMPEG_LIB_PATH})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
avformat
|
||||
avcodec
|
||||
avutil
|
||||
swscale
|
||||
)
|
||||
# copy
|
||||
set(FFMPEG_BIN_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/bin/${QC_CPU_ARCH}")
|
||||
get_target_property(FFMPEG_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FFMPEG_BIN_PATH}/avcodec-58.dll" "${FFMPEG_BIN_OUTPUT_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FFMPEG_BIN_PATH}/avformat-58.dll" "${FFMPEG_BIN_OUTPUT_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FFMPEG_BIN_PATH}/avutil-56.dll" "${FFMPEG_BIN_OUTPUT_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FFMPEG_BIN_PATH}/swscale-5.dll" "${FFMPEG_BIN_OUTPUT_PATH}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${FFMPEG_BIN_PATH}/swresample-3.dll" "${FFMPEG_BIN_OUTPUT_PATH}"
|
||||
)
|
||||
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(QSC_DEPLOY_PATH ${QSC_BIN_OUTPUT_PATH})
|
||||
endif()
|
||||
|
||||
# MacOS
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
# ffmpeg
|
||||
# include
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ../third_party/ffmpeg/include)
|
||||
# link
|
||||
set(FFMPEG_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib")
|
||||
target_link_directories(${PROJECT_NAME} PRIVATE ${FFMPEG_LIB_PATH})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
avformat.58
|
||||
avcodec.58
|
||||
avutil.56
|
||||
swscale.5
|
||||
)
|
||||
|
||||
# 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)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
# dylib,scrcpy-server,adb copy to Contents/MacOS
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/libavcodec.58.dylib" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/libavformat.58.dylib" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/libavutil.56.dylib" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/libswscale.5.dylib" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib/libswresample.3.dylib" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/scrcpy-server" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/adb/mac/adb" "${MACOS_BUNDLE_PATH}/MacOS"
|
||||
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"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../config/config.ini" "${MACOS_BUNDLE_PATH}/MacOS/config/config.ini"
|
||||
)
|
||||
|
||||
# Step 2. ues MACOSX_PACKAGE_LOCATION copy icns to Resources
|
||||
|
@ -408,20 +281,13 @@ 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})
|
||||
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# include
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ../third_party/ffmpeg/include)
|
||||
# link
|
||||
set(FFMPEG_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../third_party/ffmpeg/lib")
|
||||
target_link_directories(${PROJECT_NAME} PRIVATE ${FFMPEG_LIB_PATH})
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
# ffmpeg
|
||||
avformat
|
||||
avcodec
|
||||
avutil
|
||||
swscale
|
||||
# qx11
|
||||
Qt${QT_VERSION_MAJOR}::X11Extras
|
||||
# xcb https://doc.qt.io/qt-5/linux-requirements.html
|
||||
|
@ -431,4 +297,17 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|||
)
|
||||
|
||||
# linux set app icon: https://blog.csdn.net/MrNoboday/article/details/82870853
|
||||
endif()
|
||||
endif()
|
||||
|
||||
#
|
||||
# common deps
|
||||
#
|
||||
|
||||
add_subdirectory(QtScrcpyCore)
|
||||
|
||||
# Qt
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
Qt${QT_VERSION_MAJOR}::Network
|
||||
QtScrcpyCore
|
||||
)
|
||||
|
|
|
@ -1,218 +0,0 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# 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/x64/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/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/x86/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/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/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/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/debug
|
||||
} else {
|
||||
DESTDIR = $$PWD/../output/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
QtScrcpy/QtScrcpyCore
Submodule
1
QtScrcpy/QtScrcpyCore
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 7ac8571daadf5af9b548a4c02eb7c6d504af6a97
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/adbprocess.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/adbprocess.cpp
|
|
@ -1,244 +0,0 @@
|
|||
#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;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList devicesInfoList = m_standardOutput.split(QRegExp("\r\n|\n"), QString::SkipEmptyParts);
|
||||
#endif
|
||||
for (QString deviceInfo : devicesInfoList) {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), Qt::SkipEmptyParts);
|
||||
#else
|
||||
QStringList deviceInfos = deviceInfo.split(QRegExp("\t"), QString::SkipEmptyParts);
|
||||
#endif
|
||||
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);
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
#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,2 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/qscrcpyevent.h
|
|
@ -1,22 +0,0 @@
|
|||
#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
|
|
@ -1,3 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/input.h \
|
||||
$$PWD/keycodes.h
|
|
@ -1,840 +0,0 @@
|
|||
// 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
|
|
@ -1,746 +0,0 @@
|
|||
// 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
|
|
@ -1,262 +0,0 @@
|
|||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
|
||||
#include "controller.h"
|
||||
#include "controlmsg.h"
|
||||
#include "inputconvertgame.h"
|
||||
#include "receiver.h"
|
||||
#include "videosocket.h"
|
||||
|
||||
Controller::Controller(std::function<qint64(const QByteArray&)> sendData, QString gameScript, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_sendData(sendData)
|
||||
{
|
||||
m_receiver = new Receiver(this);
|
||||
Q_ASSERT(m_receiver);
|
||||
|
||||
updateScript(gameScript);
|
||||
}
|
||||
|
||||
Controller::~Controller() {}
|
||||
|
||||
void Controller::postControlMsg(ControlMsg *controlMsg)
|
||||
{
|
||||
if (controlMsg) {
|
||||
QCoreApplication::postEvent(this, controlMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::recvDeviceMsg(DeviceMsg *deviceMsg)
|
||||
{
|
||||
if (!m_receiver) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_receiver->recvDeviceMsg(deviceMsg);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool Controller::isCurrentCustomKeymap()
|
||||
{
|
||||
if (!m_inputConvert) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_inputConvert->isCurrentCustomKeymap();
|
||||
}
|
||||
|
||||
void Controller::onPostBackOrScreenOn(bool down)
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_BACK_OR_SCREEN_ON);
|
||||
controlMsg->setBackOrScreenOnData(down);
|
||||
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::onCopy()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_COPY);
|
||||
}
|
||||
|
||||
void Controller::onCut()
|
||||
{
|
||||
postKeyCodeClick(AKEYCODE_CUT);
|
||||
}
|
||||
|
||||
void Controller::onExpandNotificationPanel()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_EXPAND_NOTIFICATION_PANEL);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onCollapsePanel()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_COLLAPSE_PANELS);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onRequestDeviceClipboard()
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onGetDeviceClipboard(bool cut)
|
||||
{
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_GET_CLIPBOARD);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
ControlMsg::GetClipboardCopyKey copyKey = cut ? ControlMsg::GCCK_CUT : ControlMsg::GCCK_COPY;
|
||||
controlMsg->setGetClipboardMsgData(copyKey);
|
||||
postControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void Controller::onSetDeviceClipboard(bool pause)
|
||||
{
|
||||
QClipboard *board = QApplication::clipboard();
|
||||
QString text = board->text();
|
||||
ControlMsg *controlMsg = new ControlMsg(ControlMsg::CMT_SET_CLIPBOARD);
|
||||
if (!controlMsg) {
|
||||
return;
|
||||
}
|
||||
controlMsg->setSetClipboardMsgData(text, pause);
|
||||
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_sendData) {
|
||||
len = static_cast<qint32>(m_sendData(buffer));
|
||||
}
|
||||
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, 0, AMETA_NONE);
|
||||
postControlMsg(controlEventDown);
|
||||
|
||||
ControlMsg *controlEventUp = new ControlMsg(ControlMsg::CMT_INJECT_KEYCODE);
|
||||
if (!controlEventUp) {
|
||||
return;
|
||||
}
|
||||
controlEventUp->setInjectKeycodeMsgData(AKEY_EVENT_ACTION_UP, keycode, 0, AMETA_NONE);
|
||||
postControlMsg(controlEventUp);
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
|
||||
#ifndef CONTROLLER_H
|
||||
#define CONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "inputconvertbase.h"
|
||||
|
||||
class QTcpSocket;
|
||||
class Receiver;
|
||||
class InputConvertBase;
|
||||
class DeviceMsg;
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Controller(std::function<qint64(const QByteArray&)> sendData, QString gameScript = "", QObject *parent = Q_NULLPTR);
|
||||
virtual ~Controller();
|
||||
|
||||
void postControlMsg(ControlMsg *controlMsg);
|
||||
void recvDeviceMsg(DeviceMsg *deviceMsg);
|
||||
void test(QRect rc);
|
||||
|
||||
void updateScript(QString gameScript = "");
|
||||
bool isCurrentCustomKeymap();
|
||||
|
||||
public slots:
|
||||
void onPostGoBack();
|
||||
void onPostGoHome();
|
||||
void onPostGoMenu();
|
||||
void onPostAppSwitch();
|
||||
void onPostPower();
|
||||
void onPostVolumeUp();
|
||||
void onPostVolumeDown();
|
||||
void onCopy();
|
||||
void onCut();
|
||||
void onExpandNotificationPanel();
|
||||
void onCollapsePanel();
|
||||
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
|
||||
// If the screen is off, it is turned on only on down
|
||||
void onPostBackOrScreenOn(bool down);
|
||||
void onRequestDeviceClipboard();
|
||||
void onGetDeviceClipboard(bool cut = false);
|
||||
void onSetDeviceClipboard(bool pause = true);
|
||||
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<Receiver> m_receiver;
|
||||
QPointer<InputConvertBase> m_inputConvert;
|
||||
std::function<qint64(const QByteArray&)> m_sendData = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // CONTROLLER_H
|
|
@ -1,14 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/controller.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/controller.cpp
|
||||
|
||||
include ($$PWD/receiver/receiver.pri)
|
||||
include ($$PWD/inputconvert/inputconvert.pri)
|
||||
|
||||
INCLUDEPATH += \
|
||||
$$PWD/receiver \
|
||||
$$PWD/inputconvert
|
||||
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
#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, quint32 repeat, AndroidMetastate metastate)
|
||||
{
|
||||
m_data.injectKeycode.action = action;
|
||||
m_data.injectKeycode.keycode = keycode;
|
||||
m_data.injectKeycode.repeat = repeat;
|
||||
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::setGetClipboardMsgData(ControlMsg::GetClipboardCopyKey copyKey)
|
||||
{
|
||||
m_data.getClipboard.copyKey = copyKey;
|
||||
}
|
||||
|
||||
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;
|
||||
m_data.setClipboard.sequence = 0;
|
||||
}
|
||||
|
||||
void ControlMsg::setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode)
|
||||
{
|
||||
m_data.setScreenPowerMode.mode = mode;
|
||||
}
|
||||
|
||||
void ControlMsg::setBackOrScreenOnData(bool down)
|
||||
{
|
||||
m_data.backOrScreenOn.action = down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP;
|
||||
}
|
||||
|
||||
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.repeat);
|
||||
BufferUtil::write32(buffer, m_data.injectKeycode.metastate);
|
||||
break;
|
||||
case CMT_INJECT_TEXT:
|
||||
BufferUtil::write32(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_BACK_OR_SCREEN_ON:
|
||||
buffer.putChar(m_data.backOrScreenOn.action);
|
||||
break;
|
||||
case CMT_GET_CLIPBOARD:
|
||||
buffer.putChar(m_data.getClipboard.copyKey);
|
||||
break;
|
||||
case CMT_SET_CLIPBOARD:
|
||||
BufferUtil::write64(buffer, m_data.setClipboard.sequence);
|
||||
buffer.putChar(!!m_data.setClipboard.paste);
|
||||
BufferUtil::write32(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_EXPAND_NOTIFICATION_PANEL:
|
||||
case CMT_EXPAND_SETTINGS_PANEL:
|
||||
case CMT_COLLAPSE_PANELS:
|
||||
case CMT_ROTATE_DEVICE:
|
||||
break;
|
||||
default:
|
||||
qDebug() << "Unknown event type:" << m_data.type;
|
||||
break;
|
||||
}
|
||||
buffer.close();
|
||||
return byteArray;
|
||||
}
|
|
@ -1,136 +0,0 @@
|
|||
#ifndef CONTROLMSG_H
|
||||
#define CONTROLMSG_H
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QRect>
|
||||
#include <QString>
|
||||
|
||||
#include "input.h"
|
||||
#include "keycodes.h"
|
||||
#include "qscrcpyevent.h"
|
||||
|
||||
#define CONTROL_MSG_MAX_SIZE (1 << 18) // 256k
|
||||
|
||||
#define CONTROL_MSG_INJECT_TEXT_MAX_LENGTH 300
|
||||
// type: 1 byte; paste flag: 1 byte; length: 4 bytes
|
||||
#define CONTROL_MSG_CLIPBOARD_TEXT_MAX_LENGTH \
|
||||
(CONTROL_MSG_MAX_SIZE - 6)
|
||||
|
||||
#define POINTER_ID_MOUSE static_cast<quint64>(-1)
|
||||
#define POINTER_ID_VIRTUAL_FINGER UINT64_C(-2)
|
||||
|
||||
// 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_EXPAND_SETTINGS_PANEL,
|
||||
CMT_COLLAPSE_PANELS,
|
||||
CMT_GET_CLIPBOARD,
|
||||
CMT_SET_CLIPBOARD,
|
||||
CMT_SET_SCREEN_POWER_MODE,
|
||||
CMT_ROTATE_DEVICE
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
enum GetClipboardCopyKey {
|
||||
GCCK_NONE,
|
||||
GCCK_COPY,
|
||||
GCCK_CUT,
|
||||
};
|
||||
|
||||
ControlMsg(ControlMsgType controlMsgType);
|
||||
virtual ~ControlMsg();
|
||||
|
||||
void setInjectKeycodeMsgData(AndroidKeyeventAction action, AndroidKeycode keycode, quint32 repeat, 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 setGetClipboardMsgData(ControlMsg::GetClipboardCopyKey copyKey);
|
||||
void setSetClipboardMsgData(QString &text, bool paste);
|
||||
void setSetScreenPowerModeData(ControlMsg::ScreenPowerMode mode);
|
||||
void setBackOrScreenOnData(bool down);
|
||||
|
||||
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;
|
||||
quint32 repeat;
|
||||
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
|
||||
{
|
||||
AndroidKeyeventAction action; // action for the BACK key
|
||||
// screen may only be turned on on ACTION_DOWN
|
||||
} backOrScreenOn;
|
||||
struct
|
||||
{
|
||||
enum GetClipboardCopyKey copyKey;
|
||||
} getClipboard;
|
||||
struct
|
||||
{
|
||||
uint64_t sequence = 0;
|
||||
char *text = Q_NULLPTR;
|
||||
bool paste = true;
|
||||
} setClipboard;
|
||||
struct
|
||||
{
|
||||
ScreenPowerMode mode;
|
||||
} setScreenPowerMode;
|
||||
};
|
||||
|
||||
ControlMsgData() {}
|
||||
~ControlMsgData() {}
|
||||
};
|
||||
|
||||
ControlMsgData m_data;
|
||||
};
|
||||
|
||||
#endif // CONTROLMSG_H
|
|
@ -1,17 +0,0 @@
|
|||
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
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
#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);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
#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;
|
||||
virtual bool isCurrentCustomKeymap()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
signals:
|
||||
void grabCursor(bool grab);
|
||||
|
||||
protected:
|
||||
void sendControlMsg(ControlMsg *msg);
|
||||
|
||||
QPointer<Controller> m_controller;
|
||||
// Qt reports repeated events as a boolean, but Android expects the actual
|
||||
// number of repetitions. This variable keeps track of the count.
|
||||
unsigned m_repeat = 0;
|
||||
};
|
||||
|
||||
#endif // INPUTCONVERTBASE_H
|
|
@ -1,665 +0,0 @@
|
|||
#include <QDebug>
|
||||
#include <QCursor>
|
||||
#include <QGuiApplication>
|
||||
#include <QTimer>
|
||||
#include <QTime>
|
||||
#include <QRandomGenerator>
|
||||
|
||||
#include "inputconvertgame.h"
|
||||
|
||||
#define CURSOR_POS_CHECK 50
|
||||
|
||||
InputConvertGame::InputConvertGame(Controller *controller) : InputConvertNormal(controller) {
|
||||
m_ctrlSteerWheel.delayData.timer = new QTimer(this);
|
||||
m_ctrlSteerWheel.delayData.timer->setSingleShot(true);
|
||||
connect(m_ctrlSteerWheel.delayData.timer, &QTimer::timeout, this, &InputConvertGame::onSteerWheelTimer);
|
||||
}
|
||||
|
||||
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 (m_keyMap.isValidMouseMoveMap() && 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_CLICK_MULTI:
|
||||
processKeyClickMulti(node.data.clickMulti.keyNode.delayClickNodes, node.data.clickMulti.keyNode.delayClickNodesCount, 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);
|
||||
}
|
||||
}
|
||||
|
||||
bool InputConvertGame::isCurrentCustomKeymap()
|
||||
{
|
||||
return m_gameMap;
|
||||
}
|
||||
|
||||
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()) {
|
||||
#ifdef QT_NO_DEBUG
|
||||
// show size change, resize grab cursor
|
||||
emit grabCursor(true);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
QPoint absolutePos = calcFrameAbsolutePos(pos).toPoint();
|
||||
static QPoint lastAbsolutePos = absolutePos;
|
||||
if (AMOTION_EVENT_ACTION_MOVE == action && lastAbsolutePos == absolutePos) {
|
||||
delete controlMsg;
|
||||
return;
|
||||
}
|
||||
lastAbsolutePos = absolutePos;
|
||||
|
||||
controlMsg->setInjectTouchMsgData(static_cast<quint64>(id), action,
|
||||
static_cast<AndroidMotioneventButtons>(0),
|
||||
QRect(absolutePos, m_frameSize),
|
||||
AMOTION_EVENT_ACTION_DOWN == action? 1.0f : 0.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::getDelayQueue(const QPointF& start, const QPointF& end,
|
||||
const double& distanceStep, const double& posStepconst,
|
||||
quint32 lowestTimer, quint32 highestTimer,
|
||||
QQueue<QPointF>& queuePos, QQueue<quint32>& queueTimer) {
|
||||
double x1 = start.x();
|
||||
double y1 = start.y();
|
||||
double x2 = end.x();
|
||||
double y2 = end.y();
|
||||
|
||||
double dx=x2-x1;
|
||||
double dy=y2-y1;
|
||||
double e=(fabs(dx)>fabs(dy))?fabs(dx):fabs(dy);
|
||||
e /= distanceStep;
|
||||
dx/=e;
|
||||
dy/=e;
|
||||
|
||||
QQueue<QPointF> queue;
|
||||
QQueue<quint32> queue2;
|
||||
for(int i=1;i<=e;i++) {
|
||||
QPointF pos(x1+(QRandomGenerator::global()->bounded(posStepconst*2)-posStepconst), y1+(QRandomGenerator::global()->bounded(posStepconst*2)-posStepconst));
|
||||
queue.enqueue(pos);
|
||||
queue2.enqueue(QRandomGenerator::global()->bounded(lowestTimer, highestTimer));
|
||||
x1+=dx;
|
||||
y1+=dy;
|
||||
}
|
||||
|
||||
queuePos = queue;
|
||||
queueTimer = queue2;
|
||||
}
|
||||
|
||||
void InputConvertGame::onSteerWheelTimer() {
|
||||
if(m_ctrlSteerWheel.delayData.queuePos.empty()) {
|
||||
return;
|
||||
}
|
||||
int id = getTouchID(m_ctrlSteerWheel.touchKey);
|
||||
m_ctrlSteerWheel.delayData.currentPos = m_ctrlSteerWheel.delayData.queuePos.dequeue();
|
||||
sendTouchMoveEvent(id, m_ctrlSteerWheel.delayData.currentPos);
|
||||
|
||||
if(m_ctrlSteerWheel.delayData.queuePos.empty() && m_ctrlSteerWheel.delayData.pressedNum == 0) {
|
||||
sendTouchUpEvent(id, m_ctrlSteerWheel.delayData.currentPos);
|
||||
detachTouchID(m_ctrlSteerWheel.touchKey);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!m_ctrlSteerWheel.delayData.queuePos.empty()) {
|
||||
m_ctrlSteerWheel.delayData.timer->start(m_ctrlSteerWheel.delayData.queueTimer.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
m_ctrlSteerWheel.delayData.pressedNum = pressedNum;
|
||||
|
||||
// last key release and timer no active, active timer to detouch
|
||||
if (pressedNum == 0) {
|
||||
if (m_ctrlSteerWheel.delayData.timer->isActive()) {
|
||||
m_ctrlSteerWheel.delayData.timer->stop();
|
||||
m_ctrlSteerWheel.delayData.queueTimer.clear();
|
||||
m_ctrlSteerWheel.delayData.queuePos.clear();
|
||||
}
|
||||
|
||||
sendTouchUpEvent(getTouchID(m_ctrlSteerWheel.touchKey), m_ctrlSteerWheel.delayData.currentPos);
|
||||
detachTouchID(m_ctrlSteerWheel.touchKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// process steer wheel key event
|
||||
m_ctrlSteerWheel.delayData.timer->stop();
|
||||
m_ctrlSteerWheel.delayData.queueTimer.clear();
|
||||
m_ctrlSteerWheel.delayData.queuePos.clear();
|
||||
|
||||
// first press, get key and touch down
|
||||
if (pressedNum == 1 && flag) {
|
||||
m_ctrlSteerWheel.touchKey = from->key();
|
||||
int id = attachTouchID(m_ctrlSteerWheel.touchKey);
|
||||
sendTouchDownEvent(id, node.data.steerWheel.centerPos);
|
||||
|
||||
getDelayQueue(node.data.steerWheel.centerPos, node.data.steerWheel.centerPos+offset,
|
||||
0.01f, 0.002f, 2, 8,
|
||||
m_ctrlSteerWheel.delayData.queuePos,
|
||||
m_ctrlSteerWheel.delayData.queueTimer);
|
||||
} else {
|
||||
getDelayQueue(m_ctrlSteerWheel.delayData.currentPos, node.data.steerWheel.centerPos+offset,
|
||||
0.01f, 0.002f, 2, 8,
|
||||
m_ctrlSteerWheel.delayData.queuePos,
|
||||
m_ctrlSteerWheel.delayData.queueTimer);
|
||||
}
|
||||
m_ctrlSteerWheel.delayData.timer->start();
|
||||
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::processKeyClickMulti(const KeyMap::DelayClickNode *nodes, const int count, const QKeyEvent *from)
|
||||
{
|
||||
if (QEvent::KeyPress != from->type()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int key = from->key();
|
||||
int delay = 0;
|
||||
QPointF clickPos;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
delay += nodes[i].delay;
|
||||
clickPos = nodes[i].pos;
|
||||
QTimer::singleShot(delay, this, [this, key, clickPos]() {
|
||||
int id = attachTouchID(key);
|
||||
sendTouchDownEvent(id, clickPos);
|
||||
});
|
||||
|
||||
// Don't up it too fast
|
||||
delay += 20;
|
||||
QTimer::singleShot(delay, this, [this, key, clickPos]() {
|
||||
int id = getTouchID(key);
|
||||
sendTouchUpEvent(id, clickPos);
|
||||
detachTouchID(key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::onDragTimer() {
|
||||
if(m_dragDelayData.queuePos.empty()) {
|
||||
return;
|
||||
}
|
||||
int id = getTouchID(m_dragDelayData.pressKey);
|
||||
m_dragDelayData.currentPos = m_dragDelayData.queuePos.dequeue();
|
||||
sendTouchMoveEvent(id, m_dragDelayData.currentPos);
|
||||
|
||||
if(m_dragDelayData.queuePos.empty()) {
|
||||
delete m_dragDelayData.timer;
|
||||
m_dragDelayData.timer = nullptr;
|
||||
|
||||
sendTouchUpEvent(id, m_dragDelayData.currentPos);
|
||||
detachTouchID(m_dragDelayData.pressKey);
|
||||
|
||||
m_dragDelayData.currentPos = QPointF();
|
||||
m_dragDelayData.pressKey = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!m_dragDelayData.queuePos.empty()) {
|
||||
m_dragDelayData.timer->start(m_dragDelayData.queueTimer.dequeue());
|
||||
}
|
||||
}
|
||||
|
||||
void InputConvertGame::processKeyDrag(const QPointF &startPos, QPointF endPos, const QKeyEvent *from)
|
||||
{
|
||||
if (QEvent::KeyPress == from->type()) {
|
||||
// stop last
|
||||
if (m_dragDelayData.timer && m_dragDelayData.timer->isActive()) {
|
||||
m_dragDelayData.timer->stop();
|
||||
delete m_dragDelayData.timer;
|
||||
m_dragDelayData.timer = nullptr;
|
||||
m_dragDelayData.queuePos.clear();
|
||||
m_dragDelayData.queueTimer.clear();
|
||||
|
||||
sendTouchUpEvent(getTouchID(m_dragDelayData.pressKey), m_dragDelayData.currentPos);
|
||||
detachTouchID(m_dragDelayData.pressKey);
|
||||
|
||||
m_dragDelayData.currentPos = QPointF();
|
||||
m_dragDelayData.pressKey = 0;
|
||||
}
|
||||
|
||||
// start this
|
||||
int id = attachTouchID(from->key());
|
||||
sendTouchDownEvent(id, startPos);
|
||||
|
||||
m_dragDelayData.timer = new QTimer(this);
|
||||
m_dragDelayData.timer->setSingleShot(true);
|
||||
connect(m_dragDelayData.timer, &QTimer::timeout, this, &InputConvertGame::onDragTimer);
|
||||
m_dragDelayData.pressKey = from->key();
|
||||
m_dragDelayData.currentPos = startPos;
|
||||
m_dragDelayData.queuePos.clear();
|
||||
m_dragDelayData.queueTimer.clear();
|
||||
getDelayQueue(startPos, endPos,
|
||||
0.01f, 0.002f, 0, 2,
|
||||
m_dragDelayData.queuePos,
|
||||
m_dragDelayData.queueTimer);
|
||||
m_dragDelayData.timer->start();
|
||||
}
|
||||
}
|
||||
|
||||
// -------- 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_raw{from->localPos() - m_ctrlMouseMove.lastPos};
|
||||
QPointF speedRatio {m_keyMap.getMouseMoveMap().data.mouseMove.speedRatio};
|
||||
QPointF distance {distance_raw.x() / speedRatio.x(), distance_raw.y() / speedRatio.y()};
|
||||
|
||||
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.05 || m_ctrlMouseMove.lastConverPos.x() > 0.95 || m_ctrlMouseMove.lastConverPos.y() < 0.05
|
||||
|| m_ctrlMouseMove.lastConverPos.y() > 0.95) {
|
||||
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(500);
|
||||
}
|
||||
|
||||
void InputConvertGame::stopMouseMoveTimer()
|
||||
{
|
||||
if (0 != m_ctrlMouseMove.timer) {
|
||||
killTimer(m_ctrlMouseMove.timer);
|
||||
m_ctrlMouseMove.timer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool InputConvertGame::switchGameMap()
|
||||
{
|
||||
m_gameMap = !m_gameMap;
|
||||
qInfo() << tr("current keymap mode: %1").arg(m_gameMap ? tr("custom") : tr("normal"));
|
||||
|
||||
if (!m_keyMap.isValidMouseMoveMap()) {
|
||||
return m_gameMap;
|
||||
}
|
||||
#ifdef QT_NO_DEBUG
|
||||
// grab cursor and set cursor only mouse move map
|
||||
emit grabCursor(m_gameMap);
|
||||
#endif
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -1,126 +0,0 @@
|
|||
#ifndef INPUTCONVERTGAME_H
|
||||
#define INPUTCONVERTGAME_H
|
||||
|
||||
#include <QPointF>
|
||||
#include <QQueue>
|
||||
|
||||
#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);
|
||||
virtual bool isCurrentCustomKeymap();
|
||||
|
||||
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);
|
||||
|
||||
// click mutil
|
||||
void processKeyClickMulti(const KeyMap::DelayClickNode *nodes, const int count, 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);
|
||||
|
||||
void getDelayQueue(const QPointF& start, const QPointF& end,
|
||||
const double& distanceStep, const double& posStepconst,
|
||||
quint32 lowestTimer, quint32 highestTimer,
|
||||
QQueue<QPointF>& queuePos, QQueue<quint32>& queueTimer);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *event);
|
||||
|
||||
private slots:
|
||||
void onSteerWheelTimer();
|
||||
void onDragTimer();
|
||||
|
||||
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 delay
|
||||
struct {
|
||||
QPointF currentPos;
|
||||
QTimer* timer = nullptr;
|
||||
QQueue<QPointF> queuePos;
|
||||
QQueue<quint32> queueTimer;
|
||||
int pressedNum = 0;
|
||||
} delayData;
|
||||
} m_ctrlSteerWheel;
|
||||
|
||||
// mouse move
|
||||
struct
|
||||
{
|
||||
QPointF lastConverPos;
|
||||
QPointF lastPos = { 0.0, 0.0 };
|
||||
bool touching = false;
|
||||
int timer = 0;
|
||||
bool smallEyes = false;
|
||||
} m_ctrlMouseMove;
|
||||
|
||||
// for drag delay
|
||||
struct {
|
||||
QPointF currentPos;
|
||||
QTimer* timer = nullptr;
|
||||
QQueue<QPointF> queuePos;
|
||||
QQueue<quint32> queueTimer;
|
||||
int pressKey = 0;
|
||||
} m_dragDelayData;
|
||||
};
|
||||
|
||||
#endif // INPUTCONVERTGAME_H
|
|
@ -1,423 +0,0 @@
|
|||
#include <cmath>
|
||||
#include <QDebug>
|
||||
|
||||
#include "inputconvertnormal.h"
|
||||
#include "controller.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),
|
||||
AMOTION_EVENT_ACTION_DOWN == action ? 1.0f : 0.0f);
|
||||
sendControlMsg(controlMsg);
|
||||
}
|
||||
|
||||
void InputConvertNormal::wheelEvent(const QWheelEvent *from, const QSize &frameSize, const QSize &showSize)
|
||||
{
|
||||
if (!from || from->angleDelta().isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// delta
|
||||
qint32 hScroll = from->angleDelta().x() == 0 ? 0 : from->angleDelta().x() / abs(from->angleDelta().x()) * 2;
|
||||
qint32 vScroll = from->angleDelta().y() == 0 ? 0 : from->angleDelta().y() / abs(from->angleDelta().y()) * 2;
|
||||
|
||||
// pos
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
|
||||
QPointF pos = from->position();
|
||||
#else
|
||||
QPointF pos = from->posF();
|
||||
#endif
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool repeat = from->isAutoRepeat();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (repeat) {
|
||||
m_repeat++;
|
||||
} else {
|
||||
m_repeat = 0;
|
||||
}
|
||||
|
||||
controlMsg->setInjectKeycodeMsgData(action, keyCode, m_repeat, 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 (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
if (buttonState & Qt::MiddleButton) {
|
||||
#else
|
||||
if (buttonState & Qt::MidButton) {
|
||||
#endif
|
||||
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);
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
#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
|
|
@ -1,533 +0,0 @@
|
|||
#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;
|
||||
|
||||
bool have_speedRatio = false;
|
||||
|
||||
// General speedRatio (for backwards compatibility)
|
||||
if (checkItemDouble(mouseMoveMap, "speedRatio")) {
|
||||
float ratio = static_cast<float>(getItemDouble(mouseMoveMap, "speedRatio"));
|
||||
keyMapNode.data.mouseMove.speedRatio.setX(ratio);
|
||||
keyMapNode.data.mouseMove.speedRatio.setY(ratio / 2.25f); // Phone screens are often FHD+
|
||||
have_speedRatio = true;
|
||||
}
|
||||
|
||||
// Individual X Ratio
|
||||
if (checkItemDouble(mouseMoveMap, "speedRatioX")) {
|
||||
keyMapNode.data.mouseMove.speedRatio.setX(static_cast<float>(getItemDouble(mouseMoveMap, "speedRatioX")));
|
||||
have_speedRatio = true;
|
||||
}
|
||||
|
||||
// Individual Y Ratio
|
||||
if (checkItemDouble(mouseMoveMap, "speedRatioY")) {
|
||||
keyMapNode.data.mouseMove.speedRatio.setY(static_cast<float>(getItemDouble(mouseMoveMap, "speedRatioY")));
|
||||
have_speedRatio = true;
|
||||
}
|
||||
|
||||
if (!have_speedRatio) {
|
||||
errorString = QString("json error: speedRatio setting is missing in mouseMoveMap!");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
// Sanity check: No ratio must be lower than 0.001
|
||||
if ( ( keyMapNode.data.mouseMove.speedRatio.x() < 0.001f ) || ( keyMapNode.data.mouseMove.speedRatio.x() < 0.001f ) ) {
|
||||
errorString = QString("json error: Minimum speedRatio is 0.001");
|
||||
goto parseError;
|
||||
}
|
||||
|
||||
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_CLICK_MULTI: {
|
||||
// safe check
|
||||
if (!checkForClickMulti(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.clickMulti.keyNode.type = key.first;
|
||||
keyMapNode.data.clickMulti.keyNode.key = key.second;
|
||||
|
||||
QJsonArray clickNodes = node.value("clickNodes").toArray();
|
||||
QJsonObject clickNode;
|
||||
keyMapNode.data.clickMulti.keyNode.delayClickNodesCount = 0;
|
||||
|
||||
for (int i = 0; i < clickNodes.size(); i++) {
|
||||
if (i >= MAX_DELAY_CLICK_NODES) {
|
||||
qInfo() << "clickNodes too much, up to " << MAX_DELAY_CLICK_NODES;
|
||||
break;
|
||||
}
|
||||
clickNode = clickNodes.at(i).toObject();
|
||||
DelayClickNode delayClickNode;
|
||||
delayClickNode.delay = getItemDouble(clickNode, "delay");
|
||||
delayClickNode.pos = getItemPos(clickNode, "pos");
|
||||
keyMapNode.data.clickMulti.keyNode.delayClickNodes[i] = delayClickNode;
|
||||
keyMapNode.data.clickMulti.keyNode.delayClickNodesCount++;
|
||||
}
|
||||
|
||||
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() << tr("Script updated, current keymap mode:normal, Press ~ key to switch keymap mode");
|
||||
|
||||
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_CLICK_MULTI: {
|
||||
QMultiHash<int, KeyMapNode *> &m = node.data.clickMulti.keyNode.type == AT_KEY ? m_rmapKey : m_rmapMouse;
|
||||
m.insert(node.data.clickMulti.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::checkForClickMulti(const QJsonObject &node)
|
||||
{
|
||||
bool ret = true;
|
||||
|
||||
if (!node.contains("clickNodes") || !node.value("clickNodes").isArray()) {
|
||||
qWarning("json error: no find clickNodes");
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray clickNodes = node.value("clickNodes").toArray();
|
||||
QJsonObject clickNode;
|
||||
int size = clickNodes.size();
|
||||
if (0 == size) {
|
||||
qWarning("json error: clickNodes is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (!clickNodes.at(i).isObject()) {
|
||||
qWarning("json error: clickNodes node must be json object");
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
|
||||
clickNode = clickNodes.at(i).toObject();
|
||||
if (!checkForDelayClickNode(clickNode)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool KeyMap::checkForDelayClickNode(const QJsonObject &node)
|
||||
{
|
||||
return checkItemPos(node, "pos") && checkItemDouble(node, "delay");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
#ifndef KEYMAP_H
|
||||
#define KEYMAP_H
|
||||
#include <QJsonObject>
|
||||
#include <QMetaEnum>
|
||||
#include <QMultiHash>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QVector>
|
||||
|
||||
#define MAX_DELAY_CLICK_NODES 50
|
||||
|
||||
class KeyMap : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum KeyMapType
|
||||
{
|
||||
KMT_INVALID = -1,
|
||||
KMT_CLICK = 0,
|
||||
KMT_CLICK_TWICE,
|
||||
KMT_CLICK_MULTI,
|
||||
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 DelayClickNode
|
||||
{
|
||||
int delay = 0;
|
||||
QPointF pos = QPointF(0, 0);
|
||||
};
|
||||
|
||||
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
|
||||
DelayClickNode delayClickNodes[MAX_DELAY_CLICK_NODES]; // for multi clicks
|
||||
int delayClickNodesCount = 0;
|
||||
|
||||
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
|
||||
{
|
||||
KeyNode keyNode;
|
||||
} clickMulti;
|
||||
struct
|
||||
{
|
||||
QPointF centerPos = { 0.0, 0.0 };
|
||||
KeyNode left, right, up, down;
|
||||
} steerWheel;
|
||||
struct
|
||||
{
|
||||
KeyNode keyNode;
|
||||
} drag;
|
||||
struct
|
||||
{
|
||||
QPointF startPos = { 0.0, 0.0 };
|
||||
QPointF speedRatio = { 1.0, 1.0 };
|
||||
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 checkForClickMulti(const QJsonObject &node);
|
||||
bool checkForDelayClickNode(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
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/keymap.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/keymap.cpp
|
|
@ -1,66 +0,0 @@
|
|||
#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 < 5) {
|
||||
// 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::read32(buf);
|
||||
if (clipboardLen > len - 5) {
|
||||
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 = 5 + clipboardLen;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qWarning("Unsupported device msg type: %d", (int)m_data.type);
|
||||
ret = -1; // error, we cannot recover
|
||||
}
|
||||
|
||||
buf.close();
|
||||
return ret;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#ifndef DEVICEMSG_H
|
||||
#define DEVICEMSG_H
|
||||
|
||||
#include <QBuffer>
|
||||
|
||||
#define DEVICE_MSG_MAX_SIZE (1 << 18) // 256k
|
||||
// type: 1 byte; length: 4 bytes
|
||||
#define DEVICE_MSG_TEXT_MAX_LENGTH (DEVICE_MSG_MAX_SIZE - 5)
|
||||
|
||||
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
|
|
@ -1,30 +0,0 @@
|
|||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
|
||||
#include "devicemsg.h"
|
||||
#include "receiver.h"
|
||||
|
||||
Receiver::Receiver(QObject *parent) : QObject(parent) {}
|
||||
|
||||
Receiver::~Receiver() {}
|
||||
|
||||
void Receiver::recvDeviceMsg(DeviceMsg *deviceMsg)
|
||||
{
|
||||
switch (deviceMsg->type()) {
|
||||
case DeviceMsg::DMT_GET_CLIPBOARD: {
|
||||
qInfo("Device clipboard copied");
|
||||
QClipboard *board = QApplication::clipboard();
|
||||
QString text;
|
||||
deviceMsg->getClipboardMsgData(text);
|
||||
|
||||
if (board->text() == text) {
|
||||
qDebug("Computer clipboard unchanged");
|
||||
break;
|
||||
}
|
||||
board->setText(text);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef RECEIVER_H
|
||||
#define RECEIVER_H
|
||||
|
||||
#include <QPointer>
|
||||
|
||||
class DeviceMsg;
|
||||
class Receiver : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Receiver(QObject *parent = Q_NULLPTR);
|
||||
virtual ~Receiver();
|
||||
|
||||
void recvDeviceMsg(DeviceMsg *deviceMsg);
|
||||
};
|
||||
|
||||
#endif // RECEIVER_H
|
|
@ -1,7 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/devicemsg.h \
|
||||
$$PWD/receiver.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/devicemsg.cpp \
|
||||
$$PWD/receiver.cpp
|
|
@ -1,74 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
#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
|
|
@ -1,155 +0,0 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "compat.h"
|
||||
#include "decoder.h"
|
||||
#include "videobuffer.h"
|
||||
|
||||
Decoder::Decoder(std::function<void(int, int, uint8_t*, uint8_t*, uint8_t*, int, int, int)> onFrame, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_vb(new VideoBuffer())
|
||||
, m_onFrame(onFrame)
|
||||
{
|
||||
m_vb->init();
|
||||
connect(this, &Decoder::newFrame, this, &Decoder::onNewFrame, Qt::QueuedConnection);
|
||||
connect(m_vb, &VideoBuffer::updateFPS, this, &Decoder::updateFPS);
|
||||
}
|
||||
|
||||
Decoder::~Decoder() {
|
||||
m_vb->deInit();
|
||||
delete m_vb;
|
||||
}
|
||||
|
||||
bool Decoder::open()
|
||||
{
|
||||
// codec
|
||||
AVCodec *codec = Q_NULLPTR;
|
||||
codec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (!codec) {
|
||||
qCritical("H.264 decoder not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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_vb) {
|
||||
m_vb->interrupt();
|
||||
}
|
||||
|
||||
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::peekFrame(std::function<void (int, int, uint8_t *)> onFrame)
|
||||
{
|
||||
if (!m_vb) {
|
||||
return;
|
||||
}
|
||||
m_vb->peekRenderedFrame(onFrame);
|
||||
}
|
||||
|
||||
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 newFrame();
|
||||
}
|
||||
|
||||
void Decoder::onNewFrame() {
|
||||
if (!m_onFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_vb->lock();
|
||||
const AVFrame *frame = m_vb->consumeRenderedFrame();
|
||||
m_onFrame(frame->width, frame->height, frame->data[0], frame->data[1], frame->data[2], frame->linesize[0], frame->linesize[1], frame->linesize[2]);
|
||||
m_vb->unLock();
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
#ifndef DECODER_H
|
||||
#define DECODER_H
|
||||
#include <QObject>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
}
|
||||
|
||||
class VideoBuffer;
|
||||
class Decoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Decoder(std::function<void(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV)> onFrame, QObject *parent = Q_NULLPTR);
|
||||
virtual ~Decoder();
|
||||
|
||||
bool open();
|
||||
void close();
|
||||
bool push(const AVPacket *packet);
|
||||
void peekFrame(std::function<void(int width, int height, uint8_t* dataRGB32)> onFrame);
|
||||
|
||||
signals:
|
||||
void updateFPS(quint32 fps);
|
||||
|
||||
private slots:
|
||||
void onNewFrame();
|
||||
|
||||
signals:
|
||||
void newFrame();
|
||||
|
||||
private:
|
||||
void pushFrame();
|
||||
|
||||
private:
|
||||
VideoBuffer *m_vb = Q_NULLPTR;
|
||||
AVCodecContext *m_codecCtx = Q_NULLPTR;
|
||||
bool m_isCodecCtxOpen = false;
|
||||
std::function<void(int, int, uint8_t*, uint8_t*, uint8_t*, int, int, int)> m_onFrame = Q_NULLPTR;
|
||||
};
|
||||
|
||||
#endif // DECODER_H
|
|
@ -1,11 +0,0 @@
|
|||
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
|
|
@ -1,66 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
#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
|
|
@ -1,173 +0,0 @@
|
|||
#include "videobuffer.h"
|
||||
#include "avframeconvert.h"
|
||||
extern "C"
|
||||
{
|
||||
#include "libavformat/avformat.h"
|
||||
#include "libavutil/avutil.h"
|
||||
#include "libavutil/imgutils.h"
|
||||
}
|
||||
|
||||
VideoBuffer::VideoBuffer(QObject *parent) : QObject(parent) {
|
||||
connect(&m_fpsCounter, &FpsCounter::updateFPS, this, &VideoBuffer::updateFPS);
|
||||
}
|
||||
|
||||
VideoBuffer::~VideoBuffer() {}
|
||||
|
||||
bool VideoBuffer::init()
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
void VideoBuffer::setRenderExpiredFrames(bool renderExpiredFrames)
|
||||
{
|
||||
m_renderExpiredFrames = renderExpiredFrames;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void VideoBuffer::peekRenderedFrame(std::function<void(int width, int height, uint8_t* dataRGB32)> onFrame)
|
||||
{
|
||||
if (!onFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
lock();
|
||||
auto frame = m_renderingframe;
|
||||
int width = frame->width;
|
||||
int height = frame->height;
|
||||
|
||||
// create buffer
|
||||
uint8_t* rgbBuffer = new uint8_t[width * height * 4];
|
||||
AVFrame *rgbFrame = av_frame_alloc();
|
||||
if (!rgbFrame) {
|
||||
delete [] rgbBuffer;
|
||||
return;
|
||||
}
|
||||
|
||||
// bind buffer to AVFrame
|
||||
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, rgbBuffer, AV_PIX_FMT_RGB32, width, height, 4);
|
||||
|
||||
// convert
|
||||
AVFrameConvert convert;
|
||||
convert.setSrcFrameInfo(width, height, AV_PIX_FMT_YUV420P);
|
||||
convert.setDstFrameInfo(width, height, AV_PIX_FMT_RGB32);
|
||||
bool ret = false;
|
||||
ret = convert.init();
|
||||
if (!ret) {
|
||||
delete [] rgbBuffer;
|
||||
av_free(rgbFrame);
|
||||
return;
|
||||
}
|
||||
ret = convert.convert(frame, rgbFrame);
|
||||
if (!ret) {
|
||||
delete [] rgbBuffer;
|
||||
av_free(rgbFrame);
|
||||
return;
|
||||
}
|
||||
convert.deInit();
|
||||
av_free(rgbFrame);
|
||||
unLock();
|
||||
|
||||
onFrame(width, height, rgbBuffer);
|
||||
delete [] rgbBuffer;
|
||||
}
|
||||
|
||||
void VideoBuffer::interrupt()
|
||||
{
|
||||
if (m_renderExpiredFrames) {
|
||||
m_mutex.lock();
|
||||
m_interrupted = true;
|
||||
m_mutex.unlock();
|
||||
// wake up blocking wait
|
||||
m_renderingFrameConsumedCond.wakeOne();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoBuffer::swap()
|
||||
{
|
||||
AVFrame *tmp = m_decodingFrame;
|
||||
m_decodingFrame = m_renderingframe;
|
||||
m_renderingframe = tmp;
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
#ifndef VIDEO_BUFFER_H
|
||||
#define VIDEO_BUFFER_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QWaitCondition>
|
||||
#include <QObject>
|
||||
|
||||
#include "fpscounter.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
class VideoBuffer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
VideoBuffer(QObject *parent = Q_NULLPTR);
|
||||
virtual ~VideoBuffer();
|
||||
|
||||
bool init();
|
||||
void deInit();
|
||||
void lock();
|
||||
void unLock();
|
||||
void setRenderExpiredFrames(bool renderExpiredFrames);
|
||||
|
||||
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();
|
||||
|
||||
void peekRenderedFrame(std::function<void(int width, int height, uint8_t* dataRGB32)> onFrame);
|
||||
|
||||
// wake up and avoid any blocking call
|
||||
void interrupt();
|
||||
|
||||
signals:
|
||||
void updateFPS(quint32 fps);
|
||||
|
||||
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
|
|
@ -1,421 +0,0 @@
|
|||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
|
||||
#include "config.h"
|
||||
#include "controller.h"
|
||||
#include "devicemsg.h"
|
||||
#include "decoder.h"
|
||||
#include "device.h"
|
||||
#include "filehandler.h"
|
||||
#include "mousetap/mousetap.h"
|
||||
#include "recorder.h"
|
||||
#include "server.h"
|
||||
#include "stream.h"
|
||||
#include "videoform.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_decoder = new Decoder([this](int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV) {
|
||||
if (m_videoForm) {
|
||||
m_videoForm->updateRender(width, height, dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);
|
||||
}
|
||||
}, this);
|
||||
m_fileHandler = new FileHandler(this);
|
||||
m_controller = new Controller([this](const QByteArray& buffer) -> qint64 {
|
||||
if (!m_server || !m_server->getControlSocket()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_server->getControlSocket()->write(buffer.data(), buffer.length());
|
||||
}, params.gameScript, this);
|
||||
m_videoForm = new VideoForm(params.framelessWindow, Config::getInstance().getSkin());
|
||||
m_videoForm->setDevice(this);
|
||||
}
|
||||
|
||||
m_stream = new Stream([this](quint8 *buf, qint32 bufSize) -> qint32 {
|
||||
auto videoSocket = m_server->getVideoSocket();
|
||||
if (!videoSocket) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return videoSocket->subThreadRecvData(buf, bufSize);
|
||||
}, this);
|
||||
|
||||
m_server = new Server(this);
|
||||
if (!m_params.recordFileName.trimmed().isEmpty()) {
|
||||
m_recorder = new Recorder(m_params.recordFileName);
|
||||
}
|
||||
initSignals();
|
||||
startServer();
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
if (m_server) {
|
||||
m_server->stop();
|
||||
}
|
||||
|
||||
if (m_stream) {
|
||||
m_stream->stopDecode();
|
||||
}
|
||||
|
||||
// server must stop before decoder, because decoder block main thread
|
||||
if (m_decoder) {
|
||||
m_decoder->close();
|
||||
}
|
||||
|
||||
if (m_recorder) {
|
||||
if (m_recorder->isRunning()) {
|
||||
m_recorder->stopRecorder();
|
||||
m_recorder->wait();
|
||||
}
|
||||
m_recorder->close();
|
||||
delete m_recorder;
|
||||
}
|
||||
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_decoder) {
|
||||
return;
|
||||
}
|
||||
|
||||
// screenshot
|
||||
m_decoder->peekFrame([this](int width, int height, uint8_t* dataRGB32) {
|
||||
saveFrame(width, height, dataRGB32);
|
||||
});
|
||||
}
|
||||
|
||||
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::postCopy, m_controller, &Controller::onCopy);
|
||||
connect(this, &Device::postCut, m_controller, &Controller::onCut);
|
||||
connect(this, &Device::setScreenPowerMode, m_controller, &Controller::onSetScreenPowerMode);
|
||||
connect(this, &Device::expandNotificationPanel, m_controller, &Controller::onExpandNotificationPanel);
|
||||
connect(this, &Device::collapsePanel, m_controller, &Controller::onCollapsePanel);
|
||||
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::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) {
|
||||
Q_UNUSED(deviceName);
|
||||
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();
|
||||
QString name = Config::getInstance().getNickName(m_params.serial);
|
||||
if (name.isEmpty()) {
|
||||
name = Config::getInstance().getTitle();
|
||||
}
|
||||
m_videoForm->setWindowTitle(name + "-" + m_params.serial);
|
||||
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);
|
||||
if (!m_recorder->open()) {
|
||||
qCritical("Could not open recorder");
|
||||
}
|
||||
|
||||
if (!m_recorder->startRecorder()) {
|
||||
qCritical("Could not start recorder");
|
||||
}
|
||||
}
|
||||
|
||||
// init decoder
|
||||
if (m_decoder) {
|
||||
m_decoder->open();
|
||||
}
|
||||
|
||||
// init decoder
|
||||
m_stream->startDecode();
|
||||
|
||||
// recv device msg
|
||||
connect(m_server->getControlSocket(), &QTcpSocket::readyRead, this, [this](){
|
||||
if (!m_controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto controlSocket = m_server->getControlSocket();
|
||||
while (controlSocket->bytesAvailable()) {
|
||||
QByteArray byteArray = controlSocket->peek(controlSocket->bytesAvailable());
|
||||
DeviceMsg deviceMsg;
|
||||
qint32 consume = deviceMsg.deserialize(byteArray);
|
||||
if (0 >= consume) {
|
||||
break;
|
||||
}
|
||||
controlSocket->read(consume);
|
||||
m_controller->recvDeviceMsg(&deviceMsg);
|
||||
}
|
||||
});
|
||||
|
||||
// 显示界面时才自动息屏(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";
|
||||
});
|
||||
connect(m_stream, &Stream::getFrame, this, [this](AVPacket *packet) {
|
||||
if (m_decoder && !m_decoder->push(packet)) {
|
||||
qCritical("Could not send packet to decoder");
|
||||
}
|
||||
|
||||
if (m_recorder && !m_recorder->push(packet)) {
|
||||
qCritical("Could not send packet to recorder");
|
||||
}
|
||||
}, Qt::DirectConnection);
|
||||
connect(m_stream, &Stream::getConfigFrame, this, [this](AVPacket *packet) {
|
||||
if (m_recorder && !m_recorder->push(packet)) {
|
||||
qCritical("Could not send config packet to recorder");
|
||||
}
|
||||
}, Qt::DirectConnection);
|
||||
}
|
||||
|
||||
if (m_decoder) {
|
||||
connect(m_decoder, &Decoder::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::isCurrentCustomKeymap()
|
||||
{
|
||||
if (!m_controller) {
|
||||
return false;
|
||||
}
|
||||
return m_controller->isCurrentCustomKeymap();
|
||||
}
|
||||
|
||||
bool Device::saveFrame(int width, int height, uint8_t* dataRGB32)
|
||||
{
|
||||
if (!dataRGB32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QImage rgbImage(dataRGB32, width, height, QImage::Format_RGB32);
|
||||
|
||||
// save
|
||||
QString absFilePath;
|
||||
QString fileDir(m_params.recordPath);
|
||||
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);
|
||||
int ret = rgbImage.save(absFilePath, "PNG", 100);
|
||||
if (!ret) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qInfo() << "screenshot save to " << absFilePath;
|
||||
return true;
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
#ifndef DEVICE_H
|
||||
#define DEVICE_H
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#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 recordPath = ""; // 视频保存路径
|
||||
QString serial = ""; // 设备序列号
|
||||
quint16 localPort = 27183; // reverse时本地监听端口
|
||||
quint16 maxSize = 720; // 视频分辨率
|
||||
quint32 bitRate = 2000000; // 视频比特率
|
||||
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; // 是否锁定视频方向
|
||||
bool stayAwake = false; // 是否保持唤醒
|
||||
bool framelessWindow = 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();
|
||||
|
||||
bool isCurrentCustomKeymap();
|
||||
|
||||
signals:
|
||||
void deviceDisconnect(QString serial);
|
||||
|
||||
// tool bar
|
||||
void switchFullScreen();
|
||||
void postGoBack();
|
||||
void postGoHome();
|
||||
void postGoMenu();
|
||||
void postAppSwitch();
|
||||
void postPower();
|
||||
void postVolumeUp();
|
||||
void postVolumeDown();
|
||||
void postCopy();
|
||||
void postCut();
|
||||
void setScreenPowerMode(ControlMsg::ScreenPowerMode mode);
|
||||
void expandNotificationPanel();
|
||||
void collapsePanel();
|
||||
void postBackOrScreenOn(bool down);
|
||||
void postTextInput(QString &text);
|
||||
void requestDeviceClipboard();
|
||||
void setDeviceClipboard(bool pause = true);
|
||||
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(int width, int height, uint8_t* dataRGB32);
|
||||
|
||||
private:
|
||||
// server relevant
|
||||
QPointer<Server> m_server;
|
||||
QPointer<Decoder> m_decoder;
|
||||
QPointer<Controller> m_controller;
|
||||
QPointer<FileHandler> m_fileHandler;
|
||||
QPointer<Stream> m_stream;
|
||||
Recorder *m_recorder = Q_NULLPTR;
|
||||
|
||||
// ui
|
||||
QPointer<VideoForm> m_videoForm;
|
||||
|
||||
QElapsedTimer m_startTimeCount;
|
||||
DeviceParams m_params;
|
||||
|
||||
GroupControlState m_controlState = GCS_FREE;
|
||||
};
|
||||
|
||||
#endif // DEVICE_H
|
|
@ -1,27 +0,0 @@
|
|||
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
|
|
@ -1,47 +0,0 @@
|
|||
#include "filehandler.h"
|
||||
|
||||
FileHandler::FileHandler(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
FileHandler::~FileHandler() {}
|
||||
|
||||
void FileHandler::onPushFileRequest(const QString &serial, const QString &file, const QString &devicePath)
|
||||
{
|
||||
AdbProcess* adb = new AdbProcess;
|
||||
bool isApk = false;
|
||||
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
onAdbProcessResult(adb, isApk, processResult);
|
||||
});
|
||||
|
||||
adb->push(serial, file, devicePath);
|
||||
}
|
||||
|
||||
void FileHandler::onInstallApkRequest(const QString &serial, const QString &apkFile)
|
||||
{
|
||||
AdbProcess* adb = new AdbProcess;
|
||||
bool isApk = true;
|
||||
connect(adb, &AdbProcess::adbProcessResult, this, [this, adb, isApk](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
onAdbProcessResult(adb, isApk, processResult);
|
||||
});
|
||||
|
||||
adb->install(serial, apkFile);
|
||||
}
|
||||
|
||||
void FileHandler::onAdbProcessResult(AdbProcess *adb, bool isApk, 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, isApk);
|
||||
adb->deleteLater();
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_EXEC:
|
||||
emit fileHandlerResult(FAR_SUCCESS_EXEC, isApk);
|
||||
adb->deleteLater();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
#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);
|
||||
|
||||
protected:
|
||||
void onAdbProcessResult(AdbProcess* adb, bool isApk, AdbProcess::ADB_EXEC_RESULT processResult);
|
||||
|
||||
signals:
|
||||
void fileHandlerResult(FILE_HANDLER_RESULT processResult, bool isApk = false);
|
||||
};
|
||||
|
||||
#endif // FILEHANDLER_H
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/filehandler.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/filehandler.cpp
|
|
@ -1,337 +0,0 @@
|
|||
#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()
|
||||
{
|
||||
// codec
|
||||
AVCodec* inputCodec = avcodec_find_decoder(AV_CODEC_ID_H264);
|
||||
if (!inputCodec) {
|
||||
qCritical("H.264 decoder not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
#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();
|
||||
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
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/recorder.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/recorder.cpp
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/qyuvopenglwidget.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/qyuvopenglwidget.cpp
|
|
@ -1,516 +0,0 @@
|
|||
#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();
|
||||
|
||||
if (!Config::getInstance().getLogLevel().isEmpty()) {
|
||||
args << QString("log_level=%1").arg(Config::getInstance().getLogLevel());
|
||||
}
|
||||
args << QString("max_size=%1").arg(QString::number(m_params.maxSize));
|
||||
args << QString("bit_rate=%1").arg(QString::number(m_params.bitRate));
|
||||
args << QString("max_fps=%1").arg(QString::number(m_params.maxFps));
|
||||
args << QString("lock_video_orientation=%1").arg(QString::number(m_params.lockVideoOrientation));
|
||||
args << QString("tunnel_forward=%1").arg((m_tunnelForward ? "true" : "false"));
|
||||
if (!m_params.crop.isEmpty()) {
|
||||
args << QString("crop=%1").arg(m_params.crop);
|
||||
}
|
||||
args << QString("control=%1").arg((m_params.control ? "true" : "false"));
|
||||
args << "display_id=0"; // display id
|
||||
args << "show_touches=false"; // show touch
|
||||
args << QString("stay_awake=%1").arg((m_params.stayAwake ? "true" : "false")); // stay awake
|
||||
// code option
|
||||
// https://github.com/Genymobile/scrcpy/commit/080a4ee3654a9b7e96c8ffe37474b5c21c02852a
|
||||
// <https://d.android.com/reference/android/media/MediaFormat>
|
||||
if (Config::getInstance().getCodecOptions() != "") {
|
||||
args << QString("codec_options=%1").arg(Config::getInstance().getCodecOptions());
|
||||
}
|
||||
if (Config::getInstance().getCodecName() != "") {
|
||||
args << QString("encoder_name=%1").arg(Config::getInstance().getCodecName());
|
||||
}
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,101 +0,0 @@
|
|||
#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
|
|
@ -1,9 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/server.h \
|
||||
$$PWD/tcpserver.h \
|
||||
$$PWD/videosocket.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/server.cpp \
|
||||
$$PWD/tcpserver.cpp \
|
||||
$$PWD/videosocket.cpp
|
|
@ -1,22 +0,0 @@
|
|||
#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);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
#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
|
|
@ -1,81 +0,0 @@
|
|||
#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();
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
#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
|
|
@ -1,300 +0,0 @@
|
|||
#include <QDebug>
|
||||
#include <QTime>
|
||||
|
||||
#include "compat.h"
|
||||
#include "stream.h"
|
||||
|
||||
#define BUFSIZE 0x10000
|
||||
#define HEADER_SIZE 12
|
||||
#define NO_PTS UINT64_MAX
|
||||
|
||||
typedef qint32 (*ReadPacketFunc)(void *, quint8 *, qint32);
|
||||
|
||||
Stream::Stream(std::function<qint32(quint8*, qint32)> recvData, QObject *parent)
|
||||
: QThread(parent)
|
||||
, m_recvData(recvData)
|
||||
{}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
qint32 Stream::recvData(quint8 *buf, qint32 bufSize)
|
||||
{
|
||||
if (!buf || !m_recvData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint32 len = m_recvData(buf, bufSize);
|
||||
return len;
|
||||
}
|
||||
|
||||
bool Stream::startDecode()
|
||||
{
|
||||
if (!m_recvData) {
|
||||
return false;
|
||||
}
|
||||
start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Stream::stopDecode()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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_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)
|
||||
{
|
||||
emit getConfigFrame(packet);
|
||||
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)
|
||||
{
|
||||
packet->dts = packet->pts;
|
||||
emit getFrame(packet);
|
||||
return true;
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
#ifndef STREAM_H
|
||||
#define STREAM_H
|
||||
|
||||
#include <QPointer>
|
||||
#include <QThread>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include "libavcodec/avcodec.h"
|
||||
#include "libavformat/avformat.h"
|
||||
}
|
||||
|
||||
class Stream : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Stream(std::function<qint32(quint8*, qint32)> recvData, QObject *parent = Q_NULLPTR);
|
||||
virtual ~Stream();
|
||||
|
||||
public:
|
||||
static bool init();
|
||||
static void deInit();
|
||||
|
||||
bool startDecode();
|
||||
void stopDecode();
|
||||
|
||||
signals:
|
||||
void onStreamStop();
|
||||
void getFrame(AVPacket* packet);
|
||||
void getConfigFrame(AVPacket* packet);
|
||||
|
||||
protected:
|
||||
void run();
|
||||
bool recvPacket(AVPacket *packet);
|
||||
bool pushPacket(AVPacket *packet);
|
||||
bool processConfigPacket(AVPacket *packet);
|
||||
bool parse(AVPacket *packet);
|
||||
bool processFrame(AVPacket *packet);
|
||||
qint32 recvData(quint8 *buf, qint32 bufSize);
|
||||
|
||||
private:
|
||||
std::function<qint32(quint8*, qint32)> m_recvData = 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
|
|
@ -1,6 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/stream.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/stream.cpp
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
SOURCES += \
|
||||
$$PWD/videoform.cpp \
|
||||
$$PWD/toolform.cpp
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/videoform.h \
|
||||
$$PWD/toolform.h
|
||||
|
||||
FORMS += \
|
||||
$$PWD/videoform.ui \
|
||||
$$PWD/toolform.ui
|
|
@ -1,297 +0,0 @@
|
|||
#include <QDebug>
|
||||
#include <QKeyEvent>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
|
||||
#include "devicemanage.h"
|
||||
#include "server.h"
|
||||
#include "videoform.h"
|
||||
|
||||
#define DM_MAX_DEVICES_NUM 1000
|
||||
|
||||
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::collapsePanel, client, &Device::collapsePanel);
|
||||
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);
|
||||
} 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::collapsePanel, client, &Device::collapsePanel);
|
||||
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;
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
#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
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/devicemanage.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/devicemanage.cpp
|
|
@ -1,5 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/iconhelper.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/iconhelper.cpp
|
443
QtScrcpy/groupcontroller/groupcontroller.cpp
Normal file
443
QtScrcpy/groupcontroller/groupcontroller.cpp
Normal file
|
@ -0,0 +1,443 @@
|
|||
#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::setScreenPowerMode(bool open)
|
||||
{
|
||||
for (const auto& serial : m_devices) {
|
||||
if (true == isHost(serial)) {
|
||||
continue;
|
||||
}
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(serial);
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
|
||||
device->setScreenPowerMode(open);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
56
QtScrcpy/groupcontroller/groupcontroller.h
Normal file
56
QtScrcpy/groupcontroller/groupcontroller.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
#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 setScreenPowerMode(bool open) 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
|
|
@ -9,7 +9,6 @@
|
|||
#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;
|
||||
|
@ -23,8 +22,8 @@ int main(int argc, char *argv[])
|
|||
{
|
||||
// set env
|
||||
#ifdef Q_OS_WIN32
|
||||
qputenv("QTSCRCPY_ADB_PATH", "../../../third_party/adb/win/adb.exe");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../../third_party/scrcpy-server");
|
||||
qputenv("QTSCRCPY_ADB_PATH", "D:/android/sdk/platform-tools/adb.exe");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../../QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server");
|
||||
qputenv("QTSCRCPY_KEYMAP_PATH", "../../../keymap");
|
||||
qputenv("QTSCRCPY_CONFIG_PATH", "../../../config");
|
||||
#endif
|
||||
|
@ -34,8 +33,8 @@ int main(int argc, char *argv[])
|
|||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
qputenv("QTSCRCPY_ADB_PATH", "../../third_party/adb/linux/adb");
|
||||
qputenv("QTSCRCPY_SERVER_PATH", "../../third_party/scrcpy-server");
|
||||
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_CONFIG_PATH", "../../config");
|
||||
qputenv("QTSCRCPY_KEYMAP_PATH", "../../keymap");
|
||||
#endif
|
||||
|
@ -73,7 +72,6 @@ int main(int argc, char *argv[])
|
|||
QSurfaceFormat::setDefaultFormat(varFormat);
|
||||
|
||||
g_oldMessageHandler = qInstallMessageHandler(myMessageOutput);
|
||||
Stream::init();
|
||||
QApplication a(argc, argv);
|
||||
|
||||
// windows下通过qmake VERSION变量或者rc设置版本号和应用名称后,这里可以直接拿到
|
||||
|
@ -103,6 +101,8 @@ int main(int argc, char *argv[])
|
|||
file.close();
|
||||
}
|
||||
|
||||
qsc::AdbProcess::setAdbPath(Config::getInstance().getAdbPath());
|
||||
|
||||
g_mainDlg = new Dialog {};
|
||||
g_mainDlg->show();
|
||||
|
||||
|
@ -116,8 +116,6 @@ int main(int argc, char *argv[])
|
|||
#if defined(Q_OS_WIN32) || defined(Q_OS_OSX)
|
||||
MouseTap::getInstance()->quitMouseEventTap();
|
||||
#endif
|
||||
|
||||
Stream::deInit();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
51
QtScrcpy/res/i18n/CMakeLists.txt
Normal file
51
QtScrcpy/res/i18n/CMakeLists.txt
Normal file
|
@ -0,0 +1,51 @@
|
|||
# 声明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
|
Binary file not shown.
|
@ -1,46 +1,6 @@
|
|||
<?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>
|
||||
<source>install apk</source>
|
||||
<translation>install apk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer</source>
|
||||
<translation>file transfer</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>wait current %1 to complete</source>
|
||||
<translation>wait current %1 to complete</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<source>%1 failed</source>
|
||||
<translation>%1 failed</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Dialog</name>
|
||||
<message>
|
||||
|
@ -149,10 +109,6 @@
|
|||
<source>reverse connection</source>
|
||||
<translation>reverse connection</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>auto enable</source>
|
||||
<translation type="vanished">auto enable</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>background record</source>
|
||||
<translation>background record</translation>
|
||||
|
@ -197,10 +153,6 @@
|
|||
<source>device serial:</source>
|
||||
<translation>device serial:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Config</source>
|
||||
<translation type="vanished">Config</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>bit rate:</source>
|
||||
<translation>bit rate:</translation>
|
||||
|
@ -231,31 +183,6 @@
|
|||
<source>no lock</source>
|
||||
<translation>no lock</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>warning</source>
|
||||
<translatorcomment>Warning</translatorcomment>
|
||||
<translation type="vanished">Warning</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit or set tray?</source>
|
||||
<translatorcomment>Quit or set tray?</translatorcomment>
|
||||
<translation type="vanished">Quit or set tray?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit</source>
|
||||
<translatorcomment>Quit</translatorcomment>
|
||||
<translation type="vanished">Quit</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set tray</source>
|
||||
<translatorcomment>Set tray</translatorcomment>
|
||||
<translation type="vanished">Set tray</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translatorcomment>Cancel</translatorcomment>
|
||||
<translation type="vanished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Notice</source>
|
||||
<translatorcomment>Notice</translatorcomment>
|
||||
|
@ -266,52 +193,12 @@
|
|||
<translatorcomment>Hidden here!</translatorcomment>
|
||||
<translation>Hidden here!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InputConvertGame</name>
|
||||
<message>
|
||||
<source>current keymap mode: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>custom</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>normal</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>KeyMap</name>
|
||||
<message>
|
||||
<source>Script updated, current keymap mode:normal, Press ~ key to switch keymap mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
<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>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>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. Strictly used for illegal purposes, or at your own risk. You can download it at the following address:</translation>
|
||||
</message>
|
||||
<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 type="unfinished"></translation>
|
||||
<translation>This software is completely open source and free. Use it at your own risk. You can download it at the following address:</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -328,14 +215,6 @@ You can download it at the following address:</source>
|
|||
<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>
|
||||
<source>touch switch</source>
|
||||
<translation>touch switch</translation>
|
||||
|
@ -380,31 +259,16 @@ You can download it at the following address:</source>
|
|||
<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>
|
||||
<source>file does not exist</source>
|
||||
<translation>file does not exist</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>videoForm</name>
|
||||
<message>
|
||||
<source>qrc:/qml/pinwheel.qml</source>
|
||||
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
Binary file not shown.
|
@ -1,46 +1,6 @@
|
|||
<?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>
|
||||
<source>install apk</source>
|
||||
<translation>安装apk</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>file transfer</source>
|
||||
<translation>文件传输</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>wait current %1 to complete</source>
|
||||
<translation>等待当前%1完成</translation>
|
||||
</message>
|
||||
<message>
|
||||
<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>
|
||||
<source>%1 failed</source>
|
||||
<translation>%1 失败</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Dialog</name>
|
||||
<message>
|
||||
|
@ -149,10 +109,6 @@
|
|||
<source>reverse connection</source>
|
||||
<translation>反向连接</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>auto enable</source>
|
||||
<translation type="vanished">自动启用脚本</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>background record</source>
|
||||
<translation>后台录制</translation>
|
||||
|
@ -197,10 +153,6 @@
|
|||
<source>device serial:</source>
|
||||
<translation>设备序列号:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Config</source>
|
||||
<translation type="vanished">配置</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>bit rate:</source>
|
||||
<translation>比特率:</translation>
|
||||
|
@ -231,31 +183,6 @@
|
|||
<source>no lock</source>
|
||||
<translation>不锁定</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>warning</source>
|
||||
<translatorcomment>警告</translatorcomment>
|
||||
<translation type="vanished">警告</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit or set tray?</source>
|
||||
<translatorcomment>退出还是最小化到托盘?</translatorcomment>
|
||||
<translation type="vanished">退出还是最小化到托盘?</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Quit</source>
|
||||
<translatorcomment>退出</translatorcomment>
|
||||
<translation type="vanished">退出</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set tray</source>
|
||||
<translatorcomment>最小化到系统托盘</translatorcomment>
|
||||
<translation type="vanished">最小化到系统托盘</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cancel</source>
|
||||
<translatorcomment>取消</translatorcomment>
|
||||
<translation type="vanished">取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Notice</source>
|
||||
<translatorcomment>提示</translatorcomment>
|
||||
|
@ -266,32 +193,6 @@
|
|||
<translatorcomment>安卓录屏程序隐藏在这!</translatorcomment>
|
||||
<translation>安卓录屏程序隐藏在这!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>InputConvertGame</name>
|
||||
<message>
|
||||
<source>current keymap mode: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>custom</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>normal</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>KeyMap</name>
|
||||
<message>
|
||||
<source>Script updated, current keymap mode:normal, Press ~ key to switch keymap mode</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
|
@ -314,14 +215,6 @@
|
|||
<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>
|
||||
<source>touch switch</source>
|
||||
<translation>触摸显示开关</translation>
|
||||
|
@ -366,31 +259,16 @@
|
|||
<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>
|
||||
<source>file does not exist</source>
|
||||
<translation>文件不存在</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>videoForm</name>
|
||||
<message>
|
||||
<source>qrc:/qml/pinwheel.qml</source>
|
||||
<translation type="vanished">qrc:/qml/pinwheel.qml</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
|
|
|
@ -6,39 +6,52 @@
|
|||
#include <QTimer>
|
||||
|
||||
#include "config.h"
|
||||
#include "device.h"
|
||||
#include "dialog.h"
|
||||
#include "keymap.h"
|
||||
#include "ui_dialog.h"
|
||||
#include "videoform.h"
|
||||
#include "../groupcontroller/groupcontroller.h"
|
||||
|
||||
QString s_keyMapPath = "";
|
||||
|
||||
const QString &getKeyMapPath()
|
||||
{
|
||||
if (s_keyMapPath.isEmpty()) {
|
||||
s_keyMapPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_KEYMAP_PATH"));
|
||||
QFileInfo fileInfo(s_keyMapPath);
|
||||
if (s_keyMapPath.isEmpty() || !fileInfo.isDir()) {
|
||||
s_keyMapPath = QCoreApplication::applicationDirPath() + "/keymap";
|
||||
}
|
||||
}
|
||||
return s_keyMapPath;
|
||||
}
|
||||
|
||||
Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
initUI();
|
||||
|
||||
connect(&m_adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
connect(&m_adb, &qsc::AdbProcess::adbProcessResult, this, [this](qsc::AdbProcess::ADB_EXEC_RESULT processResult) {
|
||||
QString log = "";
|
||||
bool newLine = true;
|
||||
QStringList args = m_adb.arguments();
|
||||
|
||||
switch (processResult) {
|
||||
case AdbProcess::AER_ERROR_START:
|
||||
case qsc::AdbProcess::AER_ERROR_START:
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_START:
|
||||
case qsc::AdbProcess::AER_SUCCESS_START:
|
||||
log = "adb run";
|
||||
newLine = false;
|
||||
break;
|
||||
case AdbProcess::AER_ERROR_EXEC:
|
||||
case qsc::AdbProcess::AER_ERROR_EXEC:
|
||||
//log = m_adb.getErrorOut();
|
||||
if (args.contains("ifconfig") && args.contains("wlan0")) {
|
||||
getIPbyIp();
|
||||
}
|
||||
break;
|
||||
case AdbProcess::AER_ERROR_MISSING_BINARY:
|
||||
case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:
|
||||
log = "adb not found";
|
||||
break;
|
||||
case AdbProcess::AER_SUCCESS_EXEC:
|
||||
case qsc::AdbProcess::AER_SUCCESS_EXEC:
|
||||
//log = m_adb.getStdOut();
|
||||
if (args.contains("devices")) {
|
||||
QStringList devices = m_adb.getDevicesSerialFromStdOut();
|
||||
|
@ -94,13 +107,16 @@ Dialog::Dialog(QWidget *parent) : QDialog(parent), ui(new Ui::Dialog)
|
|||
qApp->quit();
|
||||
});
|
||||
connect(m_hideIcon, &QSystemTrayIcon::activated, this, &Dialog::slotActivated);
|
||||
|
||||
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected, this, &Dialog::onDeviceConnected);
|
||||
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected, this, &Dialog::onDeviceDisconnected);
|
||||
}
|
||||
|
||||
Dialog::~Dialog()
|
||||
{
|
||||
qDebug() << "~Dialog()";
|
||||
updateBootConfig(false);
|
||||
m_deviceManage.disconnectAllDevice();
|
||||
qsc::IDeviceManage::getInstance().disconnectAllDevice();
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
@ -211,7 +227,7 @@ void Dialog::delayMs(int ms)
|
|||
|
||||
QString Dialog::getGameScript(const QString &fileName)
|
||||
{
|
||||
QFile loadFile(KeyMap::getKeyMapPath() + "/" + fileName);
|
||||
QFile loadFile(getKeyMapPath() + "/" + fileName);
|
||||
if (!loadFile.open(QIODevice::ReadOnly)) {
|
||||
outLog("open file failed:" + fileName, true);
|
||||
return "";
|
||||
|
@ -262,48 +278,37 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
// this is ok that "native" toUshort is 0
|
||||
quint16 videoSize = ui->maxSizeBox->currentText().trimmed().toUShort();
|
||||
Device::DeviceParams params;
|
||||
qsc::DeviceParams params;
|
||||
params.serial = ui->serialBox->currentText().trimmed();
|
||||
params.maxSize = videoSize;
|
||||
params.bitRate = getBitRate();
|
||||
// on devices with Android >= 10, the capture frame rate can be limited
|
||||
params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
|
||||
params.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();
|
||||
params.framelessWindow = ui->framelessCheck->isChecked();
|
||||
params.recordPath = ui->recordPathEdt->text().trimmed();
|
||||
params.recordFileFormat = ui->formatBox->currentText().trimmed();
|
||||
params.serverLocalPath = getServerPath();
|
||||
params.serverRemotePath = Config::getInstance().getServerPath();
|
||||
params.pushFilePath = Config::getInstance().getPushFilePath();
|
||||
params.gameScript = getGameScript(ui->gameBox->currentText());
|
||||
params.serverVersion = Config::getInstance().getServerVersion();
|
||||
params.logLevel = Config::getInstance().getLogLevel();
|
||||
params.codecOptions = Config::getInstance().getCodecOptions();
|
||||
params.codecName = Config::getInstance().getCodecName();
|
||||
|
||||
m_deviceManage.connectDevice(params);
|
||||
|
||||
if (ui->alwaysTopCheck->isChecked()) {
|
||||
m_deviceManage.staysOnTop(params.serial);
|
||||
}
|
||||
m_deviceManage.showFPS(params.serial, ui->fpsCheck->isChecked());
|
||||
qsc::IDeviceManage::getInstance().connectDevice(params);
|
||||
}
|
||||
|
||||
void Dialog::on_stopServerBtn_clicked()
|
||||
{
|
||||
if (m_deviceManage.disconnectDevice(ui->serialBox->currentText().trimmed())) {
|
||||
if (qsc::IDeviceManage::getInstance().disconnectDevice(ui->serialBox->currentText().trimmed())) {
|
||||
outLog("stop server");
|
||||
}
|
||||
}
|
||||
|
@ -416,6 +421,59 @@ void Dialog::getIPbyIp()
|
|||
m_adb.execute(ui->serialBox->currentText().trimmed(), adbArgs);
|
||||
}
|
||||
|
||||
void Dialog::onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size)
|
||||
{
|
||||
Q_UNUSED(deviceName);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto videoForm = new VideoForm(ui->framelessCheck->isChecked(), Config::getInstance().getSkin());
|
||||
videoForm->setSerial(serial);
|
||||
|
||||
qsc::IDeviceManage::getInstance().getDevice(serial)->setUserData(static_cast<void*>(videoForm));
|
||||
qsc::IDeviceManage::getInstance().getDevice(serial)->registerDeviceObserver(videoForm);
|
||||
|
||||
videoForm->showFPS(ui->fpsCheck->isChecked());
|
||||
if (ui->alwaysTopCheck->isChecked()) {
|
||||
videoForm->staysOnTop();
|
||||
}
|
||||
|
||||
// must be show before updateShowSize
|
||||
videoForm->show();
|
||||
QString name = Config::getInstance().getNickName(serial);
|
||||
if (name.isEmpty()) {
|
||||
name = Config::getInstance().getTitle();
|
||||
}
|
||||
videoForm->setWindowTitle(name + "-" + serial);
|
||||
videoForm->updateShowSize(size);
|
||||
|
||||
bool deviceVer = size.height() > size.width();
|
||||
QRect rc = Config::getInstance().getRect(serial);
|
||||
bool rcVer = rc.height() > rc.width();
|
||||
// same width/height rate
|
||||
if (rc.isValid() && (deviceVer == rcVer)) {
|
||||
// mark: resize is for fix setGeometry magneticwidget bug
|
||||
videoForm->resize(rc.size());
|
||||
videoForm->setGeometry(rc);
|
||||
}
|
||||
|
||||
GroupController::instance().addDevice(serial);
|
||||
}
|
||||
|
||||
void Dialog::onDeviceDisconnected(QString serial)
|
||||
{
|
||||
GroupController::instance().removeDevice(serial);
|
||||
|
||||
auto data = qsc::IDeviceManage::getInstance().getDevice(serial)->getUserData();
|
||||
if (data) {
|
||||
VideoForm* vf = static_cast<VideoForm*>(data);
|
||||
qsc::IDeviceManage::getInstance().getDevice(serial)->deRegisterDeviceObserver(vf);
|
||||
vf->close();
|
||||
vf->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
void Dialog::on_wirelessDisConnectBtn_clicked()
|
||||
{
|
||||
if (checkAdbRun()) {
|
||||
|
@ -459,13 +517,13 @@ void Dialog::on_clearOut_clicked()
|
|||
|
||||
void Dialog::on_stopAllServerBtn_clicked()
|
||||
{
|
||||
m_deviceManage.disconnectAllDevice();
|
||||
qsc::IDeviceManage::getInstance().disconnectAllDevice();
|
||||
}
|
||||
|
||||
void Dialog::on_refreshGameScriptBtn_clicked()
|
||||
{
|
||||
ui->gameBox->clear();
|
||||
QDir dir(KeyMap::getKeyMapPath());
|
||||
QDir dir(getKeyMapPath());
|
||||
if (!dir.exists()) {
|
||||
outLog("keymap directory not find", true);
|
||||
return;
|
||||
|
@ -482,7 +540,13 @@ void Dialog::on_refreshGameScriptBtn_clicked()
|
|||
|
||||
void Dialog::on_applyScriptBtn_clicked()
|
||||
{
|
||||
m_deviceManage.updateScript(getGameScript(ui->gameBox->currentText()));
|
||||
auto curSerial = ui->serialBox->currentText().trimmed();
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(curSerial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
device->updateScript(getGameScript(ui->gameBox->currentText()));
|
||||
}
|
||||
|
||||
void Dialog::on_recordScreenCheck_clicked(bool checked)
|
||||
|
@ -598,9 +662,7 @@ void Dialog::on_useSingleModeCheck_clicked()
|
|||
ui->rightWidget->show();
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, [this]() {
|
||||
resize(layout()->sizeHint().width(), height());
|
||||
});
|
||||
resize(layout()->sizeHint().width(), height());
|
||||
}
|
||||
|
||||
void Dialog::on_serialBox_currentIndexChanged(const QString &arg1)
|
||||
|
@ -611,5 +673,18 @@ void Dialog::on_serialBox_currentIndexChanged(const QString &arg1)
|
|||
quint32 Dialog::getBitRate()
|
||||
{
|
||||
return ui->bitRateEdit->text().trimmed().toUInt() *
|
||||
(ui->bitRateBox->currentText() == QString("Mbps") ? 1000000 : 1000);
|
||||
(ui->bitRateBox->currentText() == QString("Mbps") ? 1000000 : 1000);
|
||||
}
|
||||
|
||||
const QString &Dialog::getServerPath()
|
||||
{
|
||||
static QString serverPath;
|
||||
if (serverPath.isEmpty()) {
|
||||
serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH"));
|
||||
QFileInfo fileInfo(serverPath);
|
||||
if (serverPath.isEmpty() || !fileInfo.isFile()) {
|
||||
serverPath = QCoreApplication::applicationDirPath() + "/scrcpy-server";
|
||||
}
|
||||
}
|
||||
return serverPath;
|
||||
}
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
|
||||
#include "adbprocess.h"
|
||||
#include "devicemanage.h"
|
||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
|
@ -31,6 +31,9 @@ public:
|
|||
void getIPbyIp();
|
||||
|
||||
private slots:
|
||||
void onDeviceConnected(bool success, const QString& serial, const QString& deviceName, const QSize& size);
|
||||
void onDeviceDisconnected(QString serial);
|
||||
|
||||
void on_updateDevice_clicked();
|
||||
void on_startServerBtn_clicked();
|
||||
void on_stopServerBtn_clicked();
|
||||
|
@ -65,14 +68,14 @@ private:
|
|||
void slotActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
int findDeviceFromeSerialBox(bool wifi);
|
||||
quint32 getBitRate();
|
||||
const QString &getServerPath();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event);
|
||||
|
||||
private:
|
||||
Ui::Dialog *ui;
|
||||
AdbProcess m_adb;
|
||||
DeviceManage m_deviceManage;
|
||||
qsc::AdbProcess m_adb;
|
||||
QSystemTrayIcon *m_hideIcon;
|
||||
QMenu *m_menu;
|
||||
QAction *m_showWindow;
|
|
@ -324,7 +324,7 @@
|
|||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
<string notr="true">2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
|
@ -3,10 +3,11 @@
|
|||
#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)
|
||||
{
|
||||
|
@ -14,6 +15,8 @@ ToolForm::ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos) : MagneticW
|
|||
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
|
||||
//setWindowFlags(windowFlags() & ~Qt::WindowMinMaxButtonsHint);
|
||||
|
||||
updateGroupControl();
|
||||
|
||||
initStyle();
|
||||
}
|
||||
|
||||
|
@ -22,13 +25,14 @@ ToolForm::~ToolForm()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
void ToolForm::setDevice(Device *device)
|
||||
void ToolForm::setSerial(const QString &serial)
|
||||
{
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
m_device = device;
|
||||
connect(m_device, &Device::controlStateChange, this, &ToolForm::onControlStateChange);
|
||||
m_serial = serial;
|
||||
}
|
||||
|
||||
bool ToolForm::isHost()
|
||||
{
|
||||
return m_isHost;
|
||||
}
|
||||
|
||||
void ToolForm::initStyle()
|
||||
|
@ -52,20 +56,13 @@ void ToolForm::initStyle()
|
|||
|
||||
void ToolForm::updateGroupControl()
|
||||
{
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
switch (m_device->controlState()) {
|
||||
case Device::GroupControlState::GCS_FREE:
|
||||
ui->groupControlBtn->setStyleSheet("color: #DCDCDC");
|
||||
break;
|
||||
case Device::GroupControlState::GCS_HOST:
|
||||
if (m_isHost) {
|
||||
ui->groupControlBtn->setStyleSheet("color: red");
|
||||
break;
|
||||
case Device::GroupControlState::GCS_CLIENT:
|
||||
} else {
|
||||
ui->groupControlBtn->setStyleSheet("color: green");
|
||||
break;
|
||||
}
|
||||
|
||||
GroupController::instance().updateDeviceState(m_serial);
|
||||
}
|
||||
|
||||
void ToolForm::mousePressEvent(QMouseEvent *event)
|
||||
|
@ -103,129 +100,126 @@ void ToolForm::hideEvent(QHideEvent *event)
|
|||
|
||||
void ToolForm::on_fullScreenBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit m_device->switchFullScreen();
|
||||
dynamic_cast<VideoForm*>(parent())->switchFullScreen();
|
||||
}
|
||||
|
||||
void ToolForm::on_returnBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoBack();
|
||||
device->postGoBack();
|
||||
}
|
||||
|
||||
void ToolForm::on_homeBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoHome();
|
||||
device->postGoHome();
|
||||
}
|
||||
|
||||
void ToolForm::on_menuBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoMenu();
|
||||
device->postGoMenu();
|
||||
}
|
||||
|
||||
void ToolForm::on_appSwitchBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postAppSwitch();
|
||||
device->postAppSwitch();
|
||||
}
|
||||
|
||||
void ToolForm::on_powerBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postPower();
|
||||
device->postPower();
|
||||
}
|
||||
|
||||
void ToolForm::on_screenShotBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->screenshot();
|
||||
device->screenshot();
|
||||
}
|
||||
|
||||
void ToolForm::on_volumeUpBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postVolumeUp();
|
||||
device->postVolumeUp();
|
||||
}
|
||||
|
||||
void ToolForm::on_volumeDownBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postVolumeDown();
|
||||
device->postVolumeDown();
|
||||
}
|
||||
|
||||
void ToolForm::on_closeScreenBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF);
|
||||
device->setScreenPowerMode(false);
|
||||
}
|
||||
|
||||
void ToolForm::on_expandNotifyBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->expandNotificationPanel();
|
||||
device->expandNotificationPanel();
|
||||
}
|
||||
|
||||
void ToolForm::on_touchBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_showTouch = !m_showTouch;
|
||||
emit m_device->showTouch(m_showTouch);
|
||||
device->showTouch(m_showTouch);
|
||||
}
|
||||
|
||||
void ToolForm::on_groupControlBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
return;
|
||||
}
|
||||
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)
|
||||
m_isHost = !m_isHost;
|
||||
updateGroupControl();
|
||||
}
|
||||
|
||||
void ToolForm::on_openScreenBtn_clicked()
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->setScreenPowerMode(ControlMsg::SPM_NORMAL);
|
||||
device->setScreenPowerMode(true);
|
||||
}
|
|
@ -4,7 +4,7 @@
|
|||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "device.h"
|
||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
#include "magneticwidget.h"
|
||||
|
||||
namespace Ui
|
||||
|
@ -21,7 +21,8 @@ public:
|
|||
explicit ToolForm(QWidget *adsorbWidget, AdsorbPositions adsorbPos);
|
||||
~ToolForm();
|
||||
|
||||
void setDevice(Device *device);
|
||||
void setSerial(const QString& serial);
|
||||
bool isHost();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent *event);
|
||||
|
@ -45,9 +46,6 @@ private slots:
|
|||
void on_expandNotifyBtn_clicked();
|
||||
void on_touchBtn_clicked();
|
||||
void on_groupControlBtn_clicked();
|
||||
|
||||
void onControlStateChange(Device *device, Device::GroupControlState oldState, Device::GroupControlState newState);
|
||||
|
||||
void on_openScreenBtn_clicked();
|
||||
|
||||
private:
|
||||
|
@ -57,8 +55,9 @@ private:
|
|||
private:
|
||||
Ui::ToolForm *ui;
|
||||
QPoint m_dragPosition;
|
||||
QPointer<Device> m_device;
|
||||
QString m_serial;
|
||||
bool m_showTouch = false;
|
||||
bool m_isHost = false;
|
||||
};
|
||||
|
||||
#endif // TOOLFORM_H
|
|
@ -22,6 +22,9 @@
|
|||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="groupControlBtn">
|
||||
<property name="toolTip">
|
||||
<string>group control</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
|
@ -14,17 +14,12 @@
|
|||
#include <QtWidgets/QHBoxLayout>
|
||||
|
||||
#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, QWidget *parent) : QWidget(parent), ui(new Ui::videoForm), m_skin(skin)
|
||||
{
|
||||
|
@ -48,7 +43,6 @@ VideoForm::~VideoForm()
|
|||
|
||||
void VideoForm::initUI()
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
if (m_skin) {
|
||||
QPixmap phone;
|
||||
if (phone.load(":/res/phone.png")) {
|
||||
|
@ -162,11 +156,16 @@ void VideoForm::updateRender(int width, int height, uint8_t* dataY, uint8_t* dat
|
|||
m_videoWidget->updateTextures(dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);
|
||||
}
|
||||
|
||||
void VideoForm::setSerial(const QString &serial)
|
||||
{
|
||||
m_serial = serial;
|
||||
}
|
||||
|
||||
void VideoForm::showToolForm(bool show)
|
||||
{
|
||||
if (!m_toolForm) {
|
||||
m_toolForm = new ToolForm(this, ToolForm::AP_OUTSIDE_RIGHT);
|
||||
m_toolForm->setDevice(m_device);
|
||||
m_toolForm->setSerial(m_serial);
|
||||
}
|
||||
m_toolForm->move(pos().x() + geometry().width(), pos().y() + 30);
|
||||
m_toolForm->setVisible(show);
|
||||
|
@ -191,10 +190,11 @@ void VideoForm::installShortcut()
|
|||
shortcut = new QShortcut(QKeySequence("Ctrl+f"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->switchFullScreen();
|
||||
switchFullScreen();
|
||||
});
|
||||
|
||||
// resizeSquare
|
||||
|
@ -211,138 +211,151 @@ void VideoForm::installShortcut()
|
|||
shortcut = new QShortcut(QKeySequence("Ctrl+h"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoHome();
|
||||
device->postGoHome();
|
||||
});
|
||||
|
||||
// postGoBack
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+b"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoBack();
|
||||
device->postGoBack();
|
||||
});
|
||||
|
||||
// postAppSwitch
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+s"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postAppSwitch();
|
||||
emit device->postAppSwitch();
|
||||
});
|
||||
|
||||
// postGoMenu
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+m"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postGoMenu();
|
||||
device->postGoMenu();
|
||||
});
|
||||
|
||||
// postVolumeUp
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+up"), this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postVolumeUp();
|
||||
emit device->postVolumeUp();
|
||||
});
|
||||
|
||||
// postVolumeDown
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+down"), this);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postVolumeDown();
|
||||
emit device->postVolumeDown();
|
||||
});
|
||||
|
||||
// postPower
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+p"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postPower();
|
||||
emit device->postPower();
|
||||
});
|
||||
|
||||
// setScreenPowerMode(ControlMsg::SPM_OFF)
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+o"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->setScreenPowerMode(ControlMsg::SPM_OFF);
|
||||
emit device->setScreenPowerMode(false);
|
||||
});
|
||||
|
||||
// expandNotificationPanel
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+n"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->expandNotificationPanel();
|
||||
emit device->expandNotificationPanel();
|
||||
});
|
||||
|
||||
// collapsePanel
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+Shift+n"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->collapsePanel();
|
||||
emit device->collapsePanel();
|
||||
});
|
||||
|
||||
// copy
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+c"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postCopy();
|
||||
emit device->postCopy();
|
||||
});
|
||||
|
||||
// cut
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+x"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->postCut();
|
||||
emit device->postCut();
|
||||
});
|
||||
|
||||
// clipboardPaste
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+v"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->setDeviceClipboard();
|
||||
emit device->setDeviceClipboard();
|
||||
});
|
||||
|
||||
// setDeviceClipboard
|
||||
shortcut = new QShortcut(QKeySequence("Ctrl+Shift+v"), this);
|
||||
shortcut->setAutoRepeat(false);
|
||||
connect(shortcut, &QShortcut::activated, this, [this]() {
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->clipboardPaste();
|
||||
emit device->clipboardPaste();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -421,8 +434,8 @@ void VideoForm::updateShowSize(const QSize &newSize)
|
|||
showSize.setHeight(showSize.width() / m_widthHeightRatio);
|
||||
}
|
||||
|
||||
if (isFullScreen() && m_device) {
|
||||
emit m_device->switchFullScreen();
|
||||
if (isFullScreen() && qsc::IDeviceManage::getInstance().getDevice(m_serial)) {
|
||||
switchFullScreen();
|
||||
}
|
||||
|
||||
if (isMaximized()) {
|
||||
|
@ -445,7 +458,7 @@ void VideoForm::updateShowSize(const QSize &newSize)
|
|||
}
|
||||
}
|
||||
|
||||
void VideoForm::onSwitchFullScreen()
|
||||
void VideoForm::switchFullScreen()
|
||||
{
|
||||
if (isFullScreen()) {
|
||||
// 横屏全屏铺满全屏,恢复时,恢复保持宽高比
|
||||
|
@ -493,6 +506,11 @@ void VideoForm::onSwitchFullScreen()
|
|||
}
|
||||
}
|
||||
|
||||
bool VideoForm::isHost()
|
||||
{
|
||||
return m_toolForm->isHost();
|
||||
}
|
||||
|
||||
void VideoForm::updateFPS(quint32 fps)
|
||||
{
|
||||
//qDebug() << "FPS:" << fps;
|
||||
|
@ -502,6 +520,17 @@ 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;
|
||||
|
@ -517,33 +546,29 @@ 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 (m_device && !m_device->isCurrentCustomKeymap()) {
|
||||
emit m_device->postGoHome();
|
||||
if (device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoHome();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton) {
|
||||
if (m_device && !m_device->isCurrentCustomKeymap()) {
|
||||
emit m_device->postGoBack();
|
||||
if (device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoBack();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
|
||||
// debug keymap pos
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
|
@ -562,8 +587,9 @@ void VideoForm::mousePressEvent(QMouseEvent *event)
|
|||
|
||||
void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (m_dragPosition.isNull()) {
|
||||
if (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
|
@ -582,7 +608,7 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
|||
local.setY(m_videoWidget->height());
|
||||
}
|
||||
event->setLocalPos(local);
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
} else {
|
||||
m_dragPosition = QPoint(0, 0);
|
||||
}
|
||||
|
@ -590,12 +616,13 @@ void VideoForm::mouseReleaseEvent(QMouseEvent *event)
|
|||
|
||||
void VideoForm::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
} else if (!m_dragPosition.isNull()) {
|
||||
if (event->buttons() & Qt::LeftButton) {
|
||||
move(event->globalPos() - m_dragPosition);
|
||||
|
@ -606,30 +633,32 @@ 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 && m_device && !m_device->isCurrentCustomKeymap()) {
|
||||
emit m_device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);
|
||||
if (event->button() == Qt::RightButton && device && !device->isCurrentCustomKeymap()) {
|
||||
emit device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);
|
||||
}
|
||||
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
event->setLocalPos(m_videoWidget->mapFrom(this, event->localPos().toPoint()));
|
||||
emit m_device->mouseEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit 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 (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF pos = m_videoWidget->mapFrom(this, event->position().toPoint());
|
||||
|
@ -637,7 +666,7 @@ void VideoForm::wheelEvent(QWheelEvent *event)
|
|||
pos, event->globalPosition(), event->pixelDelta(), event->angleDelta(), event->buttons(), event->modifiers(), event->phase(), event->inverted());
|
||||
#else
|
||||
if (m_videoWidget->geometry().contains(event->pos())) {
|
||||
if (!m_device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF pos = m_videoWidget->mapFrom(this, event->pos());
|
||||
|
@ -646,28 +675,30 @@ void VideoForm::wheelEvent(QWheelEvent *event)
|
|||
pos, event->globalPosF(), event->pixelDelta(), event->angleDelta(), event->delta(), event->orientation(),
|
||||
event->buttons(), event->modifiers(), event->phase(), event->source(), event->inverted());
|
||||
#endif
|
||||
emit m_device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->wheelEvent(&wheelEvent, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
}
|
||||
|
||||
void VideoForm::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
if (Qt::Key_Escape == event->key() && !event->isAutoRepeat() && isFullScreen()) {
|
||||
emit m_device->switchFullScreen();
|
||||
switchFullScreen();
|
||||
}
|
||||
|
||||
emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
|
||||
void VideoForm::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit m_device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
emit device->keyEvent(event, m_videoWidget->frameSize(), m_videoWidget->size());
|
||||
}
|
||||
|
||||
void VideoForm::paintEvent(QPaintEvent *paint)
|
||||
|
@ -716,10 +747,12 @@ void VideoForm::resizeEvent(QResizeEvent *event)
|
|||
void VideoForm::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
Q_UNUSED(event)
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
Config::getInstance().setRect(m_device->getSerial(), geometry());
|
||||
Config::getInstance().setRect(device->getSerial(), geometry());
|
||||
device->disconnectDevice();
|
||||
}
|
||||
|
||||
void VideoForm::dragEnterEvent(QDragEnterEvent *event)
|
||||
|
@ -739,7 +772,8 @@ void VideoForm::dragLeaveEvent(QDragLeaveEvent *event)
|
|||
|
||||
void VideoForm::dropEvent(QDropEvent *event)
|
||||
{
|
||||
if (!m_device) {
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
const QMimeData *qm = event->mimeData();
|
||||
|
@ -755,9 +789,9 @@ void VideoForm::dropEvent(QDropEvent *event)
|
|||
}
|
||||
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit m_device->installApkRequest(file);
|
||||
emit device->installApkRequest(file);
|
||||
continue;
|
||||
}
|
||||
emit m_device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
emit device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
}
|
||||
}
|
|
@ -4,17 +4,18 @@
|
|||
#include <QPointer>
|
||||
#include <QWidget>
|
||||
|
||||
#include "../QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class videoForm;
|
||||
}
|
||||
|
||||
class ToolForm;
|
||||
class Device;
|
||||
class FileHandler;
|
||||
class QYUVOpenGLWidget;
|
||||
class QLabel;
|
||||
class VideoForm : public QWidget
|
||||
class VideoForm : public QWidget, public qsc::DeviceObserver
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -24,18 +25,22 @@ public:
|
|||
void staysOnTop(bool top = true);
|
||||
void updateShowSize(const QSize &newSize);
|
||||
void updateRender(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV, int linesizeY, int linesizeU, int linesizeV);
|
||||
void setDevice(Device *device);
|
||||
void setSerial(const QString& serial);
|
||||
QRect getGrabCursorRect();
|
||||
const QSize &frameSize();
|
||||
void resizeSquare();
|
||||
void removeBlackRect();
|
||||
void showFPS(bool show);
|
||||
void switchFullScreen();
|
||||
|
||||
public slots:
|
||||
void onSwitchFullScreen();
|
||||
void updateFPS(quint32 fps);
|
||||
bool isHost();
|
||||
|
||||
private:
|
||||
void onFrame(int width, int height, uint8_t* dataY, uint8_t* dataU, uint8_t* dataV,
|
||||
int linesizeY, int linesizeU, int linesizeV) override;
|
||||
void updateFPS(quint32 fps) override;
|
||||
void grabCursor(bool grab) override;
|
||||
|
||||
void updateStyleSheet(bool vertical);
|
||||
QMargins getMargins(bool vertical);
|
||||
void initUI();
|
||||
|
@ -46,23 +51,23 @@ private:
|
|||
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 mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QMouseEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void keyReleaseEvent(QKeyEvent *event) override;
|
||||
|
||||
void paintEvent(QPaintEvent *);
|
||||
void showEvent(QShowEvent *event);
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
void closeEvent(QCloseEvent *event);
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
void showEvent(QShowEvent *event) override;
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
void dragMoveEvent(QDragMoveEvent *event);
|
||||
void dragLeaveEvent(QDragLeaveEvent *event);
|
||||
void dropEvent(QDropEvent *event);
|
||||
void dragEnterEvent(QDragEnterEvent *event) override;
|
||||
void dragMoveEvent(QDragMoveEvent *event) override;
|
||||
void dragLeaveEvent(QDragLeaveEvent *event) override;
|
||||
void dropEvent(QDropEvent *event) override;
|
||||
|
||||
private:
|
||||
// ui
|
||||
|
@ -78,9 +83,7 @@ private:
|
|||
float m_widthHeightRatio = 0.5f;
|
||||
bool m_skin = true;
|
||||
QPoint m_fullScreenBeforePos;
|
||||
|
||||
//outside member
|
||||
QPointer<Device> m_device;
|
||||
QString m_serial;
|
||||
};
|
||||
|
||||
#endif // VIDEOFORM_H
|
|
@ -1,9 +0,0 @@
|
|||
FORMS +=
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/keepratiowidget.h \
|
||||
$$PWD/magneticwidget.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/keepratiowidget.cpp \
|
||||
$$PWD/magneticwidget.cpp
|
|
@ -1,58 +0,0 @@
|
|||
#include "bufferutil.h"
|
||||
|
||||
void BufferUtil::write32(QBuffer &buffer, quint32 value)
|
||||
{
|
||||
buffer.putChar(value >> 24);
|
||||
buffer.putChar(value >> 16);
|
||||
buffer.putChar(value >> 8);
|
||||
buffer.putChar(value);
|
||||
}
|
||||
|
||||
void BufferUtil::write64(QBuffer &buffer, quint64 value)
|
||||
{
|
||||
write32(buffer, value >> 32);
|
||||
write32(buffer, (quint32)value);
|
||||
}
|
||||
|
||||
void BufferUtil::write16(QBuffer &buffer, quint32 value)
|
||||
{
|
||||
buffer.putChar(value >> 8);
|
||||
buffer.putChar(value);
|
||||
}
|
||||
|
||||
quint16 BufferUtil::read16(QBuffer &buffer)
|
||||
{
|
||||
uchar c;
|
||||
quint16 ret = 0;
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= (c << 8);
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= c;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
quint32 BufferUtil::read32(QBuffer &buffer)
|
||||
{
|
||||
uchar c;
|
||||
quint32 ret = 0;
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= (c << 24);
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= (c << 16);
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= (c << 8);
|
||||
buffer.getChar(reinterpret_cast<char *>(&c));
|
||||
ret |= c;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
quint64 BufferUtil::read64(QBuffer &buffer)
|
||||
{
|
||||
quint32 msb = read32(buffer);
|
||||
quint32 lsb = read32(buffer);
|
||||
|
||||
return ((quint64)msb << 32) | lsb;
|
||||
;
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef BUFFERUTIL_H
|
||||
#define BUFFERUTIL_H
|
||||
#include <QBuffer>
|
||||
|
||||
class BufferUtil
|
||||
{
|
||||
public:
|
||||
static void write16(QBuffer &buffer, quint32 value);
|
||||
static void write32(QBuffer &buffer, quint32 value);
|
||||
static void write64(QBuffer &buffer, quint64 value);
|
||||
static quint16 read16(QBuffer &buffer);
|
||||
static quint32 read32(QBuffer &buffer);
|
||||
static quint64 read64(QBuffer &buffer);
|
||||
};
|
||||
|
||||
#endif // BUFFERUTIL_H
|
|
@ -1,35 +0,0 @@
|
|||
#ifndef COMPAT_H
|
||||
#define COMPAT_H
|
||||
#include "libavcodec/version.h"
|
||||
#include "libavformat/version.h"
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2016-04-11 - 6f69f7a / 9200514 - lavf 57.33.100 / 57.5.0 - avformat.h
|
||||
// Add AVStream.codecpar, deprecate AVStream.codec.
|
||||
#if (LIBAVFORMAT_VERSION_MICRO >= 100 /* FFmpeg */ && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 33, 100)) \
|
||||
|| (LIBAVFORMAT_VERSION_MICRO < 100 && /* Libav */ \
|
||||
LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 5, 0))
|
||||
#define QTSCRCPY_LAVF_HAS_NEW_CODEC_PARAMS_API
|
||||
#endif
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2018-02-06 - 0694d87024 - lavf 58.9.100 - avformat.h
|
||||
// Deprecate use of av_register_input_format(), av_register_output_format(),
|
||||
// av_register_all(), av_iformat_next(), av_oformat_next().
|
||||
// Add av_demuxer_iterate(), and av_muxer_iterate().
|
||||
#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(58, 9, 100)
|
||||
#define QTSCRCPY_LAVF_HAS_NEW_MUXER_ITERATOR_API
|
||||
#else
|
||||
#define QTSCRCPY_LAVF_REQUIRES_REGISTER_ALL
|
||||
#endif
|
||||
|
||||
// In ffmpeg/doc/APIchanges:
|
||||
// 2016-04-21 - 7fc329e - lavc 57.37.100 - avcodec.h
|
||||
// Add a new audio/video encoding and decoding API with decoupled input
|
||||
// and output -- avcodec_send_packet(), avcodec_receive_frame(),
|
||||
// avcodec_send_frame() and avcodec_receive_packet().
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 37, 100)
|
||||
#define QTSCRCPY_LAVF_HAS_NEW_ENCODING_DECODING_API
|
||||
#endif
|
||||
|
||||
#endif // COMPAT_H
|
|
@ -1,25 +0,0 @@
|
|||
HEADERS += \
|
||||
$$PWD/mousetap.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/mousetap.cpp
|
||||
|
||||
win32 {
|
||||
HEADERS += $$PWD/winmousetap.h
|
||||
SOURCES += $$PWD/winmousetap.cpp
|
||||
LIBS += -lUser32
|
||||
}
|
||||
|
||||
mac {
|
||||
HEADERS += $$PWD/cocoamousetap.h
|
||||
OBJECTIVE_SOURCES += $$PWD/cocoamousetap.mm
|
||||
LIBS += -framework Appkit
|
||||
QMAKE_CFLAGS += -mmacosx-version-min=10.6
|
||||
}
|
||||
|
||||
linux {
|
||||
HEADERS += $$PWD/xmousetap.h
|
||||
SOURCES += $$PWD/xmousetap.cpp
|
||||
LIBS += -lxcb
|
||||
QT += x11extras
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
include ($$PWD/mousetap/mousetap.pri)
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/compat.h \
|
||||
$$PWD/bufferutil.h \
|
||||
$$PWD/config.h
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/bufferutil.cpp \
|
||||
$$PWD/config.cpp
|
|
@ -246,8 +246,8 @@ All the dependencies are provided and it is easy to compile.
|
|||
### PC client
|
||||
1. Set up the Qt development environment on the target platform.
|
||||
Qt version>=5.12 (use MSVC 2019 on Windows)
|
||||
2. Clone the project
|
||||
3. Open the project root directory `all.pro` or `CMakeLists.txt` with QtCreator
|
||||
2. Clone the project (git clone --recursive git@github.com:barry-ran/QtScrcpy.git)
|
||||
3. Open the project root directory `CMakeLists.txt` with QtCreator
|
||||
4. Compile and run
|
||||
|
||||
### Android (If you do not have special requirements, you can directly use the built-in scrcpy-server.jar)
|
||||
|
@ -256,7 +256,7 @@ Qt version>=5.12 (use MSVC 2019 on Windows)
|
|||
2. Open server project in project root with Android Studio
|
||||
3. The first time you open it, if you do not have the corresponding version of gradle, you will be prompted to find gradle, whether to upgrade gradle and create it. Select Cancel. After canceling, you will be prompted to select the location of the existing gradle. You can also cancel it (it will download automatically).
|
||||
4. Edit the code as needed, but of course you do n’t need to.
|
||||
4. After compiling the apk, rename it to scrcpy-server and replace third_party/scrcpy-server.
|
||||
4. After compiling the apk, rename it to scrcpy-server and replace QtScrcpy/QtScrcpyCore/src/third_party/scrcpy-server.
|
||||
|
||||
## Licence
|
||||
Since it is based on scrcpy, respect its Licence
|
||||
|
|
|
@ -243,8 +243,8 @@ Mac OS平台,你可以直接使用我编译好的可执行程序:
|
|||
### PC端
|
||||
1. 在目标平台上搭建Qt开发环境
|
||||
Qt版本>=5.12(在Windows上使用MSVC 2019)
|
||||
2. 克隆该项目
|
||||
3. 使用QtCreator打开项目根目录`all.pro`或`CMakeLists.txt`
|
||||
2. 克隆该项目(git clone --recursive git@github.com:barry-ran/QtScrcpy.git)
|
||||
3. 使用QtCreator打开项目根目录`CMakeLists.txt`
|
||||
4. 编译,运行
|
||||
|
||||
### Android端 (没有修改需求的话直接使用自带的scrcpy-server即可)
|
||||
|
|
7
all.pro
7
all.pro
|
@ -1,7 +0,0 @@
|
|||
TEMPLATE = subdirs
|
||||
SUBDIRS = QtScrcpy
|
||||
|
||||
# 多语言翻译文件
|
||||
TRANSLATIONS = \
|
||||
$$PWD/QtScrcpy/res/i18n/zh_CN.ts \
|
||||
$$PWD/QtScrcpy/res/i18n/en_US.ts
|
3
ci/lrelease.sh
Normal file
3
ci/lrelease.sh
Normal file
|
@ -0,0 +1,3 @@
|
|||
# https://doc.qt.io/qt-5/linguist-manager.html#lrelease
|
||||
# lrelease -help
|
||||
lrelease ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
|
4
ci/lupdate.sh
Normal file
4
ci/lupdate.sh
Normal file
|
@ -0,0 +1,4 @@
|
|||
# https://doc.qt.io/qt-5/linguist-manager.html#lupdate
|
||||
# lupdate -help
|
||||
# export PATH=/D/Qt/5.15.2/msvc2019/bin:$PATH
|
||||
lupdate -no-obsolete ./QtScrcpy -ts ./QtScrcpy/res/i18n/en_US.ts ./QtScrcpy/res/i18n/zh_CN.ts
|
|
@ -40,8 +40,8 @@ echo current build mode: %cpu_mode%
|
|||
echo current publish dir: %publish_dir%
|
||||
|
||||
:: 环境变量设置
|
||||
set adb_path=%script_path%..\..\third_party\adb\win\*.*
|
||||
set jar_path=%script_path%..\..\third_party\scrcpy-server
|
||||
set adb_path=%script_path%..\..\QtScrcpy\QtScrcpyCore\src\third_party\adb\win\*.*
|
||||
set jar_path=%script_path%..\..\QtScrcpy\QtScrcpyCore\src\third_party\scrcpy-server
|
||||
set keymap_path=%script_path%..\..\keymap
|
||||
set config_path=%script_path%..\..\config
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue