mirror of
https://github.com/barry-ran/QtScrcpy.git
synced 2025-04-20 11:35:56 +00:00
feat: Add minimal QML support, add simple demo QML
app
This commit is contained in:
parent
4274042673
commit
9fa1933f46
10 changed files with 1668 additions and 5 deletions
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.19 FATAL_ERROR)
|
||||
project(all)
|
||||
|
||||
add_subdirectory(QtScrcpy)
|
||||
add_subdirectory(QtScrcpy)
|
|
@ -18,6 +18,8 @@ set(QC_PROJECT_VERSION ${QC_FILE_VERSION})
|
|||
project(${QC_PROJECT_NAME} VERSION ${QC_PROJECT_VERSION} LANGUAGES CXX)
|
||||
message(STATUS "[${PROJECT_NAME}] Project ${PROJECT_NAME} ${PROJECT_VERSION}")
|
||||
|
||||
option(BUILD_QML_DEMO "Build demo QML app" ON)
|
||||
|
||||
# QC define
|
||||
|
||||
# check arch
|
||||
|
@ -68,7 +70,12 @@ if (NOT MSVC)
|
|||
add_compile_options(-Wall -Wextra -pedantic -Werror)
|
||||
|
||||
# disable some warning
|
||||
add_compile_options(-Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
|
||||
set(QSC_COMPILE_DEFINITIONS -Wno-nested-anon-types -Wno-c++17-extensions -Wno-overloaded-virtual)
|
||||
if(BUILD_QML_DEMO)
|
||||
set(QSC_COMPILE_DEFINITIONS ${QSC_COMPILE_DEFINITIONS} -Wno-gnu-zero-variadic-macro-arguments)
|
||||
endif()
|
||||
|
||||
add_compile_options(${QSC_COMPILE_DEFINITIONS})
|
||||
endif()
|
||||
|
||||
#
|
||||
|
@ -79,8 +86,8 @@ set(CMAKE_AUTOUIC ON)
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets REQUIRED)
|
||||
find_package(QT NAMES Qt6 Qt5 COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets Quick REQUIRED)
|
||||
find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Widgets Network Multimedia OpenGL OpenGLWidgets Quick REQUIRED)
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
find_package(X11 REQUIRED)
|
||||
endif()
|
||||
|
@ -297,6 +304,8 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|||
target_link_libraries(${PROJECT_NAME} PRIVATE "-framework AppKit")
|
||||
endif()
|
||||
|
||||
qt_add_library(QtScrcpyQml STATIC)
|
||||
|
||||
# Linux
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
get_target_property(QSC_BIN_OUTPUT_PATH ${PROJECT_NAME} RUNTIME_OUTPUT_DIRECTORY)
|
||||
|
@ -313,7 +322,6 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|||
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||
# qx11
|
||||
Qt${QT_VERSION_MAJOR}::CorePrivate
|
||||
# xcb https://doc.qt.io/qt-5/linux-requirements.html
|
||||
xcb
|
||||
# pthread
|
||||
|
@ -339,3 +347,26 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
|||
Qt${QT_VERSION_MAJOR}::OpenGLWidgets
|
||||
QtScrcpyCore
|
||||
)
|
||||
|
||||
qt_add_qml_module(QtScrcpyQml
|
||||
SOURCES
|
||||
URI "QtScrcpy"
|
||||
VERSION 1.0
|
||||
SOURCES
|
||||
qml/scrcpyitem.h qml/scrcpyitem.cpp
|
||||
qml/scrcpymanager.h qml/scrcpymanager.cpp
|
||||
util/config.h
|
||||
util/config.cpp
|
||||
)
|
||||
|
||||
target_include_directories(QtScrcpyQml PRIVATE qml)
|
||||
|
||||
target_link_libraries(QtScrcpyQml PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::OpenGL
|
||||
Qt${QT_VERSION_MAJOR}::Quick
|
||||
QtScrcpyCore
|
||||
)
|
||||
|
||||
if(BUILD_QML_DEMO)
|
||||
add_subdirectory(TestQml)
|
||||
endif()
|
||||
|
|
46
QtScrcpy/TestQml/CMakeLists.txt
Normal file
46
QtScrcpy/TestQml/CMakeLists.txt
Normal file
|
@ -0,0 +1,46 @@
|
|||
qt_add_executable(appTestQml
|
||||
main.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(appTestQml
|
||||
URI TestQml
|
||||
VERSION 1.0
|
||||
QML_FILES
|
||||
Main.qml
|
||||
)
|
||||
|
||||
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
|
||||
# If you are developing for iOS or macOS you should consider setting an
|
||||
# explicit, fixed bundle identifier manually though.
|
||||
set_target_properties(appTestQml PROPERTIES
|
||||
# MACOSX_BUNDLE_GUI_IDENTIFIER com.example.appTestQml
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
|
||||
MACOSX_BUNDLE TRUE
|
||||
WIN32_EXECUTABLE TRUE
|
||||
)
|
||||
|
||||
target_link_libraries(appTestQml
|
||||
PRIVATE Qt6::Quick QtScrcpyQmlplugin
|
||||
)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(TARGETS appTestQml
|
||||
BUNDLE DESTINATION .
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(QC_CPU_ARCH x64)
|
||||
else()
|
||||
set(QC_CPU_ARCH x86)
|
||||
endif()
|
||||
|
||||
set_target_properties(appTestQml PROPERTIES
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/../../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/$<0:>"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET appTestQml POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_SOURCE_DIR}/../../config/config.ini" "${CMAKE_CURRENT_SOURCE_DIR}/../../output/${QC_CPU_ARCH}/${CMAKE_BUILD_TYPE}/config/config.ini"
|
||||
)
|
213
QtScrcpy/TestQml/Main.qml
Normal file
213
QtScrcpy/TestQml/Main.qml
Normal file
|
@ -0,0 +1,213 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtScrcpy
|
||||
|
||||
ApplicationWindow {
|
||||
id: root
|
||||
|
||||
width: 800
|
||||
height: 600
|
||||
visible: true
|
||||
title: qsTr("Scrcpy Manager")
|
||||
|
||||
color: "black"
|
||||
|
||||
RowLayout {
|
||||
id: mainLayout
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
spacing: 10
|
||||
|
||||
ListView {
|
||||
id: _list
|
||||
|
||||
Layout.preferredWidth: parent.width * 0.5
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
visible: !_scrcpyItem.visible
|
||||
enabled: !ScrcpyManager.currentlyConnecting
|
||||
opacity: enabled ? 1 : 0.5
|
||||
|
||||
spacing: 10
|
||||
model: ScrcpyManager.devicesList
|
||||
delegate: Frame {
|
||||
width: parent.width
|
||||
height: 80
|
||||
|
||||
background: Rectangle {
|
||||
color: "lightgray"
|
||||
border.color: "black"
|
||||
radius: 10
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 10
|
||||
|
||||
Text {
|
||||
text: modelData
|
||||
font.pixelSize: 20
|
||||
color: "black"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Connect"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
onClicked: {
|
||||
ScrcpyManager.connectToDevice(_scrcpyItem,
|
||||
modelData)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: settingsContainer
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: parent.width * 0.5
|
||||
|
||||
spacing: 10
|
||||
visible: !_scrcpyItem.visible
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
Label {
|
||||
text: "Size"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: maxSizeComboBox
|
||||
Layout.fillWidth: true
|
||||
model: ScrcpyManager.maxSizeArray
|
||||
currentIndex: ScrcpyManager.maxSizeIndex
|
||||
onCurrentIndexChanged: {
|
||||
ScrcpyManager.maxSizeIndex = currentIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
Label {
|
||||
text: "Rate"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: bitRateUnitsComboBox
|
||||
Layout.fillWidth: true
|
||||
model: ScrcpyManager.availableBitRatesUnits
|
||||
currentIndex: ScrcpyManager.bitRateUnits === "Mbps" ? 0 : 1
|
||||
onCurrentIndexChanged: {
|
||||
ScrcpyManager.bitRateUnits = (currentIndex === 0 ? "Mbps" : "Kbps")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
Label {
|
||||
text: "Bits"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
TextField {
|
||||
id: bitRateField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: "Bit Rate"
|
||||
text: ScrcpyManager.bitRateNumeric.toString()
|
||||
onTextChanged: {
|
||||
ScrcpyManager.bitRateNumeric = parseInt(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: 5
|
||||
Label {
|
||||
text: "Orientation"
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
}
|
||||
ComboBox {
|
||||
id: lockOrientationComboBox
|
||||
Layout.fillWidth: true
|
||||
model: ScrcpyManager.availableOrientations
|
||||
currentIndex: ScrcpyManager.lockOrientationIndex
|
||||
onCurrentIndexChanged: {
|
||||
ScrcpyManager.lockOrientationIndex = currentIndex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: autoUpdateDeviceCheck
|
||||
text: "Auto Update Device"
|
||||
checked: ScrcpyManager.autoUpdateDevice
|
||||
onToggled: {
|
||||
ScrcpyManager.autoUpdateDevice = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: _controlsContainer
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
spacing: 10
|
||||
visible: _scrcpyItem.visible
|
||||
|
||||
Button {
|
||||
text: "Disconnect"
|
||||
onClicked: {
|
||||
ScrcpyManager.disconnectFromDevice()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ScrcpyItem {
|
||||
id: _scrcpyItem
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: _controlsContainer.top
|
||||
}
|
||||
visible: false
|
||||
|
||||
onFrameSizeChanged: {
|
||||
if (visible) {
|
||||
root.width = Math.max(frameSize.width, 800)
|
||||
root.height = frameSize.height + _controlsContainer.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ScrcpyManager
|
||||
|
||||
onDeviceConnected: {
|
||||
_scrcpyItem.visible = true
|
||||
_scrcpyItem.focus = true
|
||||
}
|
||||
|
||||
onDeviceDisconnected: {
|
||||
_scrcpyItem.visible = false
|
||||
_scrcpyItem.focus = false
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
ScrcpyManager.listDevices()
|
||||
}
|
||||
}
|
13
QtScrcpy/TestQml/main.cpp
Normal file
13
QtScrcpy/TestQml/main.cpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#include <QGuiApplication>
|
||||
#include <QQmlApplicationEngine>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QGuiApplication app(argc, argv);
|
||||
|
||||
QQmlApplicationEngine engine;
|
||||
QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed, &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
|
||||
engine.loadFromModule("TestQml", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
524
QtScrcpy/qml/scrcpyitem.cpp
Normal file
524
QtScrcpy/qml/scrcpyitem.cpp
Normal file
|
@ -0,0 +1,524 @@
|
|||
#include "scrcpyitem.h"
|
||||
#include <QGuiApplication>
|
||||
#include <QPainter>
|
||||
#include <QScreen>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QOpenGLContext>
|
||||
#include <QQuickWindow>
|
||||
#include <QCoreApplication>
|
||||
#include <cstring>
|
||||
#include <QtMath>
|
||||
|
||||
#include "util/config.h"
|
||||
|
||||
ScrcpyItem::ScrcpyItem(QQuickItem *parent)
|
||||
: QQuickFramebufferObject(parent)
|
||||
{
|
||||
setMirrorVertically(false);
|
||||
setTextureFollowsItemSize(true);
|
||||
initUI();
|
||||
}
|
||||
|
||||
QQuickFramebufferObject::Renderer* ScrcpyItem::createRenderer() const
|
||||
{
|
||||
return new ScrcpyItemRenderer(const_cast<ScrcpyItem*>(this));
|
||||
}
|
||||
|
||||
void ScrcpyItem::initUI()
|
||||
{
|
||||
setAcceptTouchEvents(true);
|
||||
setAcceptedMouseButtons(Qt::AllButtons);
|
||||
}
|
||||
|
||||
void ScrcpyItem::setFrameSize(const QSize &fs)
|
||||
{
|
||||
if (m_frameSize != fs) {
|
||||
m_frameSize = fs;
|
||||
emit frameSizeChanged();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItem::updateTextures(quint8* dataY,
|
||||
quint8* dataU,
|
||||
quint8* dataV,
|
||||
quint32 linesizeY,
|
||||
quint32 linesizeU,
|
||||
quint32 linesizeV)
|
||||
{
|
||||
m_dataY = dataY;
|
||||
m_dataU = dataU;
|
||||
m_dataV = dataV;
|
||||
m_linesizeY = linesizeY;
|
||||
m_linesizeU = linesizeU;
|
||||
m_linesizeV = linesizeV;
|
||||
|
||||
m_newFrameAvailable = true;
|
||||
update();
|
||||
}
|
||||
|
||||
void ScrcpyItem::updateRender(int width, int height,
|
||||
uint8_t *dataY, uint8_t *dataU, uint8_t *dataV,
|
||||
int linesizeY, int linesizeU, int linesizeV)
|
||||
{
|
||||
setFrameSize(QSize(width, height));
|
||||
updateTextures(dataY, dataU, dataV, linesizeY, linesizeU, linesizeV);
|
||||
}
|
||||
|
||||
void ScrcpyItem::setSerial(const QString &serial)
|
||||
{
|
||||
m_serial = serial;
|
||||
}
|
||||
|
||||
QString ScrcpyItem::serial() const
|
||||
{
|
||||
return m_serial;
|
||||
}
|
||||
|
||||
void ScrcpyItem::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 ScrcpyItem::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (event->button() == Qt::MiddleButton && device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoHome();
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() == Qt::RightButton && device && !device->isCurrentCustomKeymap()) {
|
||||
device->postGoBack();
|
||||
return;
|
||||
}
|
||||
|
||||
if (boundingRect().contains(event->position())) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF mappedPos = event->position();
|
||||
emit device->mouseEvent(new QMouseEvent(event->type(),
|
||||
mappedPos,
|
||||
event->globalPosition(),
|
||||
event->button(),
|
||||
event->buttons(),
|
||||
event->modifiers()),
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
qreal x = mappedPos.x() / width();
|
||||
qreal y = mappedPos.y() / height();
|
||||
qInfo() << QString(R"("pos": {"x": %1, "y": %2})").arg(x).arg(y);
|
||||
}
|
||||
} else {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItem::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPointF local = event->position();
|
||||
|
||||
if (local.x() < 0) local.setX(0);
|
||||
if (local.x() > width()) local.setX(width());
|
||||
if (local.y() < 0) local.setY(0);
|
||||
if (local.y() > height()) local.setY(height());
|
||||
|
||||
emit device->mouseEvent(new QMouseEvent(event->type(),
|
||||
local,
|
||||
event->globalPosition(),
|
||||
event->button(),
|
||||
event->buttons(),
|
||||
event->modifiers()),
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
|
||||
void ScrcpyItem::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (boundingRect().contains(event->position())) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF mappedPos = event->position();
|
||||
emit device->mouseEvent(new QMouseEvent(event->type(),
|
||||
mappedPos,
|
||||
event->globalPosition(),
|
||||
event->button(),
|
||||
event->buttons(),
|
||||
event->modifiers()),
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItem::mouseDoubleClickEvent(QMouseEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (event->button() == Qt::RightButton && device && !device->isCurrentCustomKeymap()) {
|
||||
emit device->postBackOrScreenOn(event->type() == QEvent::MouseButtonPress);
|
||||
}
|
||||
|
||||
if (boundingRect().contains(event->position())) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF mappedPos = event->position();
|
||||
emit device->mouseEvent(new QMouseEvent(event->type(),
|
||||
mappedPos,
|
||||
event->globalPosition(),
|
||||
event->button(),
|
||||
event->buttons(),
|
||||
event->modifiers()),
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItem::wheelEvent(QWheelEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (boundingRect().contains(event->position())) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
QPointF pos = event->position();
|
||||
|
||||
QWheelEvent adjustedEvent(
|
||||
pos,
|
||||
event->globalPosition(),
|
||||
event->pixelDelta(),
|
||||
event->angleDelta(),
|
||||
event->buttons(),
|
||||
event->modifiers(),
|
||||
event->phase(),
|
||||
event->inverted()
|
||||
);
|
||||
emit device->wheelEvent(&adjustedEvent,
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItem::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit device->keyEvent(event,
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
|
||||
void ScrcpyItem::keyReleaseEvent(QKeyEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
emit device->keyEvent(event,
|
||||
QSize(frameSize().width(), frameSize().height()),
|
||||
size().toSize());
|
||||
}
|
||||
|
||||
void ScrcpyItem::dropEvent(QDropEvent *event)
|
||||
{
|
||||
auto device = qsc::IDeviceManage::getInstance().getDevice(m_serial);
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
const QMimeData *qm = event->mimeData();
|
||||
QList<QUrl> urls = qm->urls();
|
||||
|
||||
for (const QUrl &url : urls) {
|
||||
QString file = url.toLocalFile();
|
||||
QFileInfo fileInfo(file);
|
||||
if (!fileInfo.exists()) {
|
||||
continue;
|
||||
}
|
||||
if (fileInfo.isFile() && fileInfo.suffix() == "apk") {
|
||||
emit device->installApkRequest(file);
|
||||
continue;
|
||||
}
|
||||
emit device->pushFileRequest(file, Config::getInstance().getPushFilePath() + fileInfo.fileName());
|
||||
}
|
||||
}
|
||||
|
||||
static const GLfloat s_coords[20] = {
|
||||
-1.0f, -1.0f, 0.0f,
|
||||
1.0f, -1.0f, 0.0f,
|
||||
-1.0f, 1.0f, 0.0f,
|
||||
1.0f, 1.0f, 0.0f,
|
||||
|
||||
0.0f, 0.0f,
|
||||
1.0f, 0.0f,
|
||||
0.0f, 1.0f,
|
||||
1.0f, 1.0f
|
||||
};
|
||||
|
||||
const GLfloat ScrcpyItemRenderer::s_coordinates[20] = {
|
||||
s_coords[0], s_coords[1], s_coords[2], s_coords[3], s_coords[4],
|
||||
s_coords[5], s_coords[6], s_coords[7], s_coords[8], s_coords[9],
|
||||
s_coords[10], s_coords[11], s_coords[12], s_coords[13], s_coords[14],
|
||||
s_coords[15], s_coords[16], s_coords[17], s_coords[18], s_coords[19]
|
||||
};
|
||||
|
||||
const char* ScrcpyItemRenderer::s_vertShaderSrc = R"(
|
||||
attribute vec3 vertexIn;
|
||||
attribute vec2 textureIn;
|
||||
varying vec2 textureOut;
|
||||
void main(void)
|
||||
{
|
||||
gl_Position = vec4(vertexIn, 1.0);
|
||||
textureOut = textureIn;
|
||||
}
|
||||
)";
|
||||
|
||||
const char* ScrcpyItemRenderer::s_fragShaderSrc = R"(
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
precision mediump int;
|
||||
#endif
|
||||
|
||||
varying vec2 textureOut;
|
||||
uniform sampler2D textureY;
|
||||
uniform sampler2D textureU;
|
||||
uniform sampler2D textureV;
|
||||
|
||||
void main(void)
|
||||
{
|
||||
vec3 yuv;
|
||||
vec3 rgb;
|
||||
|
||||
const vec3 Rcoeff = vec3(1.1644, 0.0000, 1.7927);
|
||||
const vec3 Gcoeff = vec3(1.1644, -0.2132, -0.5329);
|
||||
const vec3 Bcoeff = vec3(1.1644, 2.1124, 0.0000);
|
||||
|
||||
yuv.x = texture2D(textureY, textureOut).r - 0.0625;
|
||||
yuv.y = texture2D(textureU, textureOut).r - 0.5;
|
||||
yuv.z = texture2D(textureV, textureOut).r - 0.5;
|
||||
|
||||
rgb.r = dot(yuv, Rcoeff);
|
||||
rgb.g = dot(yuv, Gcoeff);
|
||||
rgb.b = dot(yuv, Bcoeff);
|
||||
|
||||
gl_FragColor = vec4(rgb, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
ScrcpyItemRenderer::ScrcpyItemRenderer(ScrcpyItem* item)
|
||||
: m_item(item)
|
||||
{
|
||||
initializeOpenGLFunctions();
|
||||
}
|
||||
|
||||
ScrcpyItemRenderer::~ScrcpyItemRenderer()
|
||||
{
|
||||
releaseGLResources();
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::synchronize(QQuickFramebufferObject* qItem)
|
||||
{
|
||||
auto item = static_cast<ScrcpyItem*>(qItem);
|
||||
if (!item) return;
|
||||
|
||||
if (item->m_newFrameAvailable) {
|
||||
m_dataY = item->m_dataY;
|
||||
m_dataU = item->m_dataU;
|
||||
m_dataV = item->m_dataV;
|
||||
m_linesizeY = item->m_linesizeY;
|
||||
m_linesizeU = item->m_linesizeU;
|
||||
m_linesizeV = item->m_linesizeV;
|
||||
m_needUpdateTextures = true;
|
||||
item->m_newFrameAvailable = false;
|
||||
}
|
||||
|
||||
if (m_localFrameSize != item->m_frameSize) {
|
||||
m_localFrameSize = item->m_frameSize;
|
||||
deInitTextures();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::render()
|
||||
{
|
||||
if (!m_shaderInited) {
|
||||
initShader();
|
||||
m_shaderInited = true;
|
||||
}
|
||||
if (!m_vboInited) {
|
||||
m_vbo.create();
|
||||
m_vbo.bind();
|
||||
m_vbo.allocate(s_coordinates, sizeof(s_coordinates));
|
||||
m_vboInited = true;
|
||||
}
|
||||
if (!m_texturesInited && m_localFrameSize.isValid()) {
|
||||
initTextures();
|
||||
m_texturesInited = true;
|
||||
}
|
||||
|
||||
glViewport(0, 0, size().width(), size().height());
|
||||
glClearColor(0, 0, 0, 1);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
if (m_needUpdateTextures && m_texturesInited) {
|
||||
if (m_dataY) updateTexture(m_textures[0], 0, m_dataY, m_linesizeY);
|
||||
if (m_dataU) updateTexture(m_textures[1], 1, m_dataU, m_linesizeU);
|
||||
if (m_dataV) updateTexture(m_textures[2], 2, m_dataV, m_linesizeV);
|
||||
m_needUpdateTextures = false;
|
||||
}
|
||||
|
||||
m_program.bind();
|
||||
m_vbo.bind();
|
||||
|
||||
m_program.enableAttributeArray("vertexIn");
|
||||
m_program.setAttributeBuffer("vertexIn",
|
||||
GL_FLOAT,
|
||||
0,
|
||||
3,
|
||||
3 * sizeof(GLfloat));
|
||||
m_program.enableAttributeArray("textureIn");
|
||||
m_program.setAttributeBuffer("textureIn",
|
||||
GL_FLOAT,
|
||||
4 * 3 * sizeof(GLfloat),
|
||||
2,
|
||||
2 * sizeof(GLfloat));
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[0]);
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[1]);
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[2]);
|
||||
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
m_program.release();
|
||||
m_vbo.release();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
QSize ScrcpyItemRenderer::size() const
|
||||
{
|
||||
return m_item->size().toSize();
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::releaseGLResources()
|
||||
{
|
||||
if (m_vboInited) {
|
||||
m_vbo.destroy();
|
||||
m_vboInited = false;
|
||||
}
|
||||
if (m_texturesInited) {
|
||||
deInitTextures();
|
||||
}
|
||||
if (m_shaderInited) {
|
||||
m_program.removeAllShaders();
|
||||
m_shaderInited = false;
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::initShader()
|
||||
{
|
||||
m_program.addShaderFromSourceCode(QOpenGLShader::Vertex, s_vertShaderSrc);
|
||||
m_program.addShaderFromSourceCode(QOpenGLShader::Fragment, s_fragShaderSrc);
|
||||
m_program.link();
|
||||
m_program.bind();
|
||||
|
||||
m_program.setUniformValue("textureY", 0);
|
||||
m_program.setUniformValue("textureU", 1);
|
||||
m_program.setUniformValue("textureV", 2);
|
||||
|
||||
m_program.release();
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::initTextures()
|
||||
{
|
||||
glGenTextures(3, m_textures);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[0]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
m_localFrameSize.width(),
|
||||
m_localFrameSize.height(),
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[1]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
m_localFrameSize.width() / 2,
|
||||
m_localFrameSize.height() / 2,
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, m_textures[2]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
m_localFrameSize.width() / 2,
|
||||
m_localFrameSize.height() / 2,
|
||||
0,
|
||||
GL_LUMINANCE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::deInitTextures()
|
||||
{
|
||||
glDeleteTextures(3, m_textures);
|
||||
std::memset(m_textures, 0, sizeof(m_textures));
|
||||
m_texturesInited = false;
|
||||
}
|
||||
|
||||
void ScrcpyItemRenderer::updateTexture(GLuint texture, int textureType,
|
||||
quint8* pixels, quint32 stride)
|
||||
{
|
||||
if (!pixels)
|
||||
return;
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
QSize planeSize = (textureType == 0) ? m_localFrameSize : (m_localFrameSize / 2);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
|
||||
glTexSubImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
0, 0,
|
||||
planeSize.width(),
|
||||
planeSize.height(),
|
||||
GL_LUMINANCE,
|
||||
GL_UNSIGNED_BYTE,
|
||||
pixels);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
}
|
133
QtScrcpy/qml/scrcpyitem.h
Normal file
133
QtScrcpy/qml/scrcpyitem.h
Normal file
|
@ -0,0 +1,133 @@
|
|||
#ifndef SCRCPYITEM_H
|
||||
#define SCRCPYITEM_H
|
||||
|
||||
#include <QQuickFramebufferObject>
|
||||
#include <QPointer>
|
||||
#include <QMargins>
|
||||
#include <QSize>
|
||||
#include <QPoint>
|
||||
#include <QMouseEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QDragEnterEvent>
|
||||
#include <QDragMoveEvent>
|
||||
#include <QDragLeaveEvent>
|
||||
#include <QDropEvent>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QOpenGLShaderProgram>
|
||||
#include <QOpenGLBuffer>
|
||||
#include "QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
|
||||
|
||||
class ScrcpyItemRenderer;
|
||||
|
||||
class ScrcpyItem : public QQuickFramebufferObject, public qsc::DeviceObserver
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QSize frameSize READ frameSize WRITE setFrameSize NOTIFY frameSizeChanged)
|
||||
|
||||
public:
|
||||
explicit ScrcpyItem(QQuickItem *parent = nullptr);
|
||||
|
||||
Renderer* createRenderer() const override;
|
||||
|
||||
QSize frameSize() const { return m_frameSize; }
|
||||
void setFrameSize(const QSize &fs);
|
||||
|
||||
Q_INVOKABLE void updateTextures(quint8* dataY,
|
||||
quint8* dataU,
|
||||
quint8* dataV,
|
||||
quint32 linesizeY,
|
||||
quint32 linesizeU,
|
||||
quint32 linesizeV);
|
||||
|
||||
void updateRender(int width, int height,
|
||||
uint8_t *dataY, uint8_t *dataU, uint8_t *dataV,
|
||||
int linesizeY, int linesizeU, int linesizeV);
|
||||
void setSerial(const QString &serial);
|
||||
|
||||
QString serial() const;
|
||||
|
||||
signals:
|
||||
void frameSizeChanged();
|
||||
|
||||
private:
|
||||
void onFrame(int width, int height,
|
||||
uint8_t* dataY, uint8_t* dataU, uint8_t* dataV,
|
||||
int linesizeY, int linesizeU, int linesizeV) override;
|
||||
|
||||
protected:
|
||||
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 dropEvent(QDropEvent *event) override;
|
||||
|
||||
private:
|
||||
void initUI();
|
||||
|
||||
QSize m_frameSize;
|
||||
bool m_newFrameAvailable = false;
|
||||
|
||||
quint8* m_dataY = nullptr;
|
||||
quint8* m_dataU = nullptr;
|
||||
quint8* m_dataV = nullptr;
|
||||
quint32 m_linesizeY = 0;
|
||||
quint32 m_linesizeU = 0;
|
||||
quint32 m_linesizeV = 0;
|
||||
|
||||
QString m_serial;
|
||||
|
||||
friend class ScrcpyItemRenderer;
|
||||
};
|
||||
|
||||
|
||||
class ScrcpyItemRenderer : public QQuickFramebufferObject::Renderer,
|
||||
protected QOpenGLFunctions
|
||||
{
|
||||
public:
|
||||
ScrcpyItemRenderer(ScrcpyItem* item);
|
||||
~ScrcpyItemRenderer() override;
|
||||
|
||||
void render() override;
|
||||
void synchronize(QQuickFramebufferObject* item) override;
|
||||
QSize size() const;
|
||||
|
||||
private:
|
||||
void releaseGLResources();
|
||||
void initShader();
|
||||
void initTextures();
|
||||
void deInitTextures();
|
||||
void updateTexture(GLuint texture, int textureType, quint8* pixels, quint32 stride);
|
||||
|
||||
ScrcpyItem* m_item = nullptr;
|
||||
|
||||
bool m_texturesInited = false;
|
||||
bool m_shaderInited = false;
|
||||
bool m_vboInited = false;
|
||||
|
||||
GLuint m_textures[3] = {0};
|
||||
QOpenGLShaderProgram m_program;
|
||||
QOpenGLBuffer m_vbo;
|
||||
|
||||
quint8* m_dataY = nullptr;
|
||||
quint8* m_dataU = nullptr;
|
||||
quint8* m_dataV = nullptr;
|
||||
quint32 m_linesizeY = 0;
|
||||
quint32 m_linesizeU = 0;
|
||||
quint32 m_linesizeV = 0;
|
||||
bool m_needUpdateTextures = false;
|
||||
|
||||
QSize m_localFrameSize;
|
||||
|
||||
static const GLfloat s_coordinates[20];
|
||||
static const char* s_vertShaderSrc;
|
||||
static const char* s_fragShaderSrc;
|
||||
};
|
||||
|
||||
#endif
|
520
QtScrcpy/qml/scrcpymanager.cpp
Normal file
520
QtScrcpy/qml/scrcpymanager.cpp
Normal file
|
@ -0,0 +1,520 @@
|
|||
#include "scrcpymanager.h"
|
||||
#include <QDebug>
|
||||
#include <QStringList>
|
||||
#include <QFileInfo>
|
||||
#include <QRandomGenerator>
|
||||
#include <QCoreApplication>
|
||||
#include "QtScrcpyCore/include/QtScrcpyCore.h"
|
||||
#include "util/config.h"
|
||||
|
||||
ScrcpyManager::ScrcpyManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(&m_adb, &qsc::AdbProcess::adbProcessResult,
|
||||
this, &ScrcpyManager::handleAdbResult);
|
||||
|
||||
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceConnected,
|
||||
this, &ScrcpyManager::onDeviceConnected);
|
||||
connect(&qsc::IDeviceManage::getInstance(), &qsc::IDeviceManage::deviceDisconnected,
|
||||
this, &ScrcpyManager::onDeviceDisconnected);
|
||||
|
||||
connect(&m_autoUpdateTimer, &QTimer::timeout, this, &ScrcpyManager::listDevices);
|
||||
|
||||
m_autoUpdateTimer.setInterval(1000);
|
||||
m_autoUpdateTimer.setSingleShot(false);
|
||||
|
||||
updateBootConfig(true);
|
||||
}
|
||||
|
||||
ScrcpyManager::~ScrcpyManager()
|
||||
{
|
||||
updateBootConfig(false);
|
||||
}
|
||||
|
||||
void ScrcpyManager::updateBootConfig(bool toView)
|
||||
{
|
||||
if (toView) {
|
||||
UserBootConfig config = Config::getInstance().getUserBootConfig();
|
||||
|
||||
if (config.bitRate == 0) {
|
||||
setBitRateUnits("Mbps");
|
||||
} else if (config.bitRate % 1000000 == 0) {
|
||||
setBitRateNumeric(config.bitRate / 1000000);
|
||||
setBitRateUnits("Mbps");
|
||||
} else {
|
||||
setBitRateNumeric(config.bitRate / 1000);
|
||||
setBitRateUnits("Kbps");
|
||||
}
|
||||
|
||||
setMaxSizeIndex(config.maxSizeIndex);
|
||||
setRecordFormatIndex(config.recordFormatIndex);
|
||||
setRecordPath(config.recordPath);
|
||||
setLockOrientationIndex(config.lockOrientationIndex);
|
||||
setFramelessWindow(config.framelessWindow);
|
||||
setRecordScreen(config.recordScreen);
|
||||
setRecordBackground(config.recordBackground);
|
||||
setReverseConnect(config.reverseConnect);
|
||||
setShowFPS(config.showFPS);
|
||||
setWindowOnTop(config.windowOnTop);
|
||||
setAutoOffScreen(config.autoOffScreen);
|
||||
setKeepAlive(config.keepAlive);
|
||||
setSimpleMode(config.simpleMode);
|
||||
setAutoUpdateDevice(config.autoUpdateDevice);
|
||||
setShowToolbar(config.showToolbar);
|
||||
} else {
|
||||
UserBootConfig config;
|
||||
|
||||
config.bitRate = (bitRateUnits() == "Mbps")
|
||||
? bitRateNumeric() * 1000000
|
||||
: bitRateNumeric() * 1000;
|
||||
|
||||
config.maxSizeIndex = maxSizeIndex();
|
||||
config.recordFormatIndex = recordFormatIndex();
|
||||
config.recordPath = recordPath();
|
||||
config.lockOrientationIndex = lockOrientationIndex();
|
||||
config.framelessWindow = framelessWindow();
|
||||
config.recordScreen = recordScreen();
|
||||
config.recordBackground = recordBackground();
|
||||
config.reverseConnect = reverseConnect();
|
||||
config.showFPS = showFPS();
|
||||
config.windowOnTop = windowOnTop();
|
||||
config.autoOffScreen = autoOffScreen();
|
||||
config.keepAlive = keepAlive();
|
||||
config.simpleMode = simpleMode();
|
||||
config.autoUpdateDevice = autoUpdateDevice();
|
||||
config.showToolbar = showToolbar();
|
||||
|
||||
Config::getInstance().setUserBootConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList ScrcpyManager::devicesList()
|
||||
{
|
||||
return m_deviceList;
|
||||
}
|
||||
|
||||
ScrcpyItem* ScrcpyManager::currentDevice() const
|
||||
{
|
||||
return m_currentItem;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::currentlyConnecting() const
|
||||
{
|
||||
return m_currentlyConnecting;
|
||||
}
|
||||
|
||||
quint32 ScrcpyManager::bitRateNumeric() const
|
||||
{
|
||||
return m_bitRateNumeric;
|
||||
}
|
||||
|
||||
QString ScrcpyManager::bitRateUnits() const
|
||||
{
|
||||
return m_bitRateUnits;
|
||||
}
|
||||
|
||||
int ScrcpyManager::maxSizeIndex() const
|
||||
{
|
||||
return m_maxSizeIndex;
|
||||
}
|
||||
|
||||
int ScrcpyManager::recordFormatIndex() const
|
||||
{
|
||||
return m_recordFormatIndex;
|
||||
}
|
||||
|
||||
QString ScrcpyManager::recordPath() const
|
||||
{
|
||||
return m_recordPath;
|
||||
}
|
||||
|
||||
int ScrcpyManager::lockOrientationIndex() const
|
||||
{
|
||||
return m_lockOrientationIndex;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::framelessWindow() const
|
||||
{
|
||||
return m_framelessWindow;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::recordScreen() const
|
||||
{
|
||||
return m_recordScreen;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::recordBackground() const
|
||||
{
|
||||
return m_recordBackground;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::reverseConnect() const
|
||||
{
|
||||
return m_reverseConnect;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::showFPS() const
|
||||
{
|
||||
return m_showFPS;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::windowOnTop() const
|
||||
{
|
||||
return m_windowOnTop;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::autoOffScreen() const
|
||||
{
|
||||
return m_autoOffScreen;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::keepAlive() const
|
||||
{
|
||||
return m_keepAlive;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::simpleMode() const
|
||||
{
|
||||
return m_simpleMode;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::autoUpdateDevice() const
|
||||
{
|
||||
return m_autoUpdateDevice;
|
||||
}
|
||||
|
||||
bool ScrcpyManager::showToolbar() const
|
||||
{
|
||||
return m_showToolbar;
|
||||
}
|
||||
|
||||
void ScrcpyManager::listDevices()
|
||||
{
|
||||
if (isAdbBusy()) {
|
||||
qWarning() << "[ScrcpyManager] ADB is busy, cannot list devices yet.";
|
||||
return;
|
||||
}
|
||||
m_adb.execute(QString(), {"devices"});
|
||||
}
|
||||
|
||||
void ScrcpyManager::connectToDevice(ScrcpyItem* item, const QString &serial)
|
||||
{
|
||||
if (!item) {
|
||||
qWarning() << "[ScrcpyManager] Invalid ScrcpyItem pointer!";
|
||||
return;
|
||||
}
|
||||
if (m_currentItem) {
|
||||
disconnectFromDevice(m_currentItem->serial());
|
||||
}
|
||||
|
||||
setCurrentlyConnecting(true);
|
||||
|
||||
qsc::DeviceParams params;
|
||||
params.serial = serial;
|
||||
|
||||
QString videoSizeStr;
|
||||
if (m_maxSizeIndex < m_maxSizeArray.size() - 1) {
|
||||
videoSizeStr = m_maxSizeArray.value(m_maxSizeIndex, "640");
|
||||
} else {
|
||||
videoSizeStr = "0";
|
||||
}
|
||||
|
||||
quint16 videoSize = static_cast<quint16>(videoSizeStr.toUShort());
|
||||
params.maxSize = videoSize;
|
||||
|
||||
if (bitRateUnits() == "Mbps") {
|
||||
params.bitRate = bitRateNumeric() * 1000000;
|
||||
} else {
|
||||
params.bitRate = bitRateNumeric() * 1000;
|
||||
}
|
||||
|
||||
params.maxFps = static_cast<quint32>(Config::getInstance().getMaxFps());
|
||||
params.closeScreen = m_autoOffScreen;
|
||||
params.useReverse = m_reverseConnect;
|
||||
params.display = !m_recordBackground;
|
||||
params.renderExpiredFrames = Config::getInstance().getRenderExpiredFrames();
|
||||
|
||||
if (m_lockOrientationIndex > 0) {
|
||||
params.captureOrientationLock = 1;
|
||||
params.captureOrientation = (m_lockOrientationIndex - 1) * 90;
|
||||
}
|
||||
|
||||
params.stayAwake = m_keepAlive;
|
||||
params.recordFile = m_recordScreen;
|
||||
params.recordPath = m_recordPath;
|
||||
|
||||
params.serverLocalPath = getServerPath();
|
||||
params.serverRemotePath = Config::getInstance().getServerPath();
|
||||
params.pushFilePath = Config::getInstance().getPushFilePath();
|
||||
params.serverVersion = Config::getInstance().getServerVersion();
|
||||
params.logLevel = Config::getInstance().getLogLevel();
|
||||
params.codecOptions = Config::getInstance().getCodecOptions();
|
||||
params.codecName = Config::getInstance().getCodecName();
|
||||
params.gameScript = QString();
|
||||
params.scid = QRandomGenerator::global()->bounded(1, 10000) & 0x7FFFFFFF;
|
||||
|
||||
qsc::IDeviceManage::getInstance().connectDevice(params);
|
||||
m_currentItem = item;
|
||||
emit currentDeviceChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::disconnectFromDevice(const QString &serial)
|
||||
{
|
||||
bool disconnected = qsc::IDeviceManage::getInstance().disconnectDevice(serial);
|
||||
if (disconnected) {
|
||||
qDebug() << "[ScrcpyManager] Disconnect requested for" << serial;
|
||||
} else {
|
||||
qWarning() << "[ScrcpyManager] No device was disconnected (maybe not found?).";
|
||||
}
|
||||
|
||||
if (m_currentItem && m_currentItem->serial() == serial) {
|
||||
m_currentItem.clear();
|
||||
emit currentDeviceChanged();
|
||||
}
|
||||
setCurrentlyConnecting(false);
|
||||
}
|
||||
|
||||
void ScrcpyManager::disconnectFromDevice()
|
||||
{
|
||||
if (m_currentItem) {
|
||||
disconnectFromDevice(m_currentItem->serial());
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyManager::onDeviceConnected(bool success, const QString &serial,
|
||||
const QString &deviceName, const QSize &size)
|
||||
{
|
||||
Q_UNUSED(deviceName)
|
||||
Q_UNUSED(size)
|
||||
|
||||
setCurrentlyConnecting(false);
|
||||
|
||||
if (!success) {
|
||||
qWarning() << "[ScrcpyManager] Device" << serial << "failed to connect.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_currentItem) {
|
||||
auto dev = qsc::IDeviceManage::getInstance().getDevice(serial);
|
||||
if (dev) {
|
||||
dev->setUserData(static_cast<void*>(m_currentItem.data()));
|
||||
dev->registerDeviceObserver(m_currentItem.data());
|
||||
m_currentItem->setSerial(serial);
|
||||
qDebug() << "[ScrcpyManager] Device connected:" << serial;
|
||||
}
|
||||
}
|
||||
emit deviceConnected(serial);
|
||||
}
|
||||
|
||||
void ScrcpyManager::onDeviceDisconnected(const QString &serial)
|
||||
{
|
||||
qDebug() << "[ScrcpyManager] Device disconnected:" << serial;
|
||||
if (m_currentItem && m_currentItem->serial() == serial) {
|
||||
m_currentItem.clear();
|
||||
emit currentDeviceChanged();
|
||||
}
|
||||
emit deviceDisconnected(serial);
|
||||
}
|
||||
|
||||
bool ScrcpyManager::isAdbBusy() { return m_adb.isRuning(); }
|
||||
|
||||
void ScrcpyManager::handleAdbResult(qsc::AdbProcess::ADB_EXEC_RESULT processResult)
|
||||
{
|
||||
switch (processResult) {
|
||||
case qsc::AdbProcess::AER_ERROR_START:
|
||||
case qsc::AdbProcess::AER_ERROR_EXEC:
|
||||
qWarning() << "[ScrcpyManager] ADB command error.";
|
||||
setCurrentlyConnecting(false);
|
||||
break;
|
||||
case qsc::AdbProcess::AER_ERROR_MISSING_BINARY:
|
||||
qWarning() << "[ScrcpyManager] ADB not found!";
|
||||
setCurrentlyConnecting(false);
|
||||
break;
|
||||
case qsc::AdbProcess::AER_SUCCESS_START:
|
||||
break;
|
||||
case qsc::AdbProcess::AER_SUCCESS_EXEC:
|
||||
if (m_adb.arguments().contains("devices")) {
|
||||
m_deviceList = m_adb.getDevicesSerialFromStdOut();
|
||||
emit devicesListChanged();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const QString &ScrcpyManager::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;
|
||||
}
|
||||
|
||||
QStringList ScrcpyManager::availableOrientations() const
|
||||
{
|
||||
return m_availableOrientations;
|
||||
}
|
||||
|
||||
QStringList ScrcpyManager::availableBitRatesUnits() const
|
||||
{
|
||||
return m_availableBitRatesUnits;
|
||||
}
|
||||
|
||||
QStringList ScrcpyManager::maxSizeArray() const
|
||||
{
|
||||
return m_maxSizeArray;
|
||||
}
|
||||
|
||||
void ScrcpyManager::setCurrentlyConnecting(bool val)
|
||||
{
|
||||
if (m_currentlyConnecting == val)
|
||||
return;
|
||||
m_currentlyConnecting = val;
|
||||
emit currentlyConnectingChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setBitRateNumeric(quint32 val)
|
||||
{
|
||||
if (m_bitRateNumeric == val)
|
||||
return;
|
||||
m_bitRateNumeric = val;
|
||||
emit bitRateNumericChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setBitRateUnits(const QString &val)
|
||||
{
|
||||
if (m_bitRateUnits == val)
|
||||
return;
|
||||
m_bitRateUnits = val;
|
||||
emit bitRateUnitsChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setMaxSizeIndex(int val)
|
||||
{
|
||||
if (m_maxSizeIndex == val)
|
||||
return;
|
||||
m_maxSizeIndex = val;
|
||||
emit maxSizeIndexChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setRecordFormatIndex(int val)
|
||||
{
|
||||
if (m_recordFormatIndex == val)
|
||||
return;
|
||||
m_recordFormatIndex = val;
|
||||
emit recordFormatIndexChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setRecordPath(const QString &val)
|
||||
{
|
||||
if (m_recordPath == val)
|
||||
return;
|
||||
m_recordPath = val;
|
||||
emit recordPathChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setLockOrientationIndex(int val)
|
||||
{
|
||||
if (m_lockOrientationIndex == val)
|
||||
return;
|
||||
m_lockOrientationIndex = val;
|
||||
emit lockOrientationIndexChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setFramelessWindow(bool val)
|
||||
{
|
||||
if (m_framelessWindow == val)
|
||||
return;
|
||||
m_framelessWindow = val;
|
||||
emit framelessWindowChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setRecordScreen(bool val)
|
||||
{
|
||||
if (m_recordScreen == val)
|
||||
return;
|
||||
m_recordScreen = val;
|
||||
emit recordScreenChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setRecordBackground(bool val)
|
||||
{
|
||||
if (m_recordBackground == val)
|
||||
return;
|
||||
m_recordBackground = val;
|
||||
emit recordBackgroundChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setReverseConnect(bool val)
|
||||
{
|
||||
if (m_reverseConnect == val)
|
||||
return;
|
||||
m_reverseConnect = val;
|
||||
emit reverseConnectChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setShowFPS(bool val)
|
||||
{
|
||||
if (m_showFPS == val)
|
||||
return;
|
||||
m_showFPS = val;
|
||||
emit showFPSChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setWindowOnTop(bool val)
|
||||
{
|
||||
if (m_windowOnTop == val)
|
||||
return;
|
||||
m_windowOnTop = val;
|
||||
emit windowOnTopChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setAutoOffScreen(bool val)
|
||||
{
|
||||
if (m_autoOffScreen == val)
|
||||
return;
|
||||
m_autoOffScreen = val;
|
||||
emit autoOffScreenChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setKeepAlive(bool val)
|
||||
{
|
||||
if (m_keepAlive == val)
|
||||
return;
|
||||
m_keepAlive = val;
|
||||
emit keepAliveChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setSimpleMode(bool val)
|
||||
{
|
||||
if (m_simpleMode == val)
|
||||
return;
|
||||
m_simpleMode = val;
|
||||
emit simpleModeChanged();
|
||||
}
|
||||
|
||||
void ScrcpyManager::setAutoUpdateDevice(bool val)
|
||||
{
|
||||
if (m_autoUpdateDevice == val)
|
||||
return;
|
||||
m_autoUpdateDevice = val;
|
||||
emit autoUpdateDeviceChanged();
|
||||
|
||||
if (m_autoUpdateDevice) {
|
||||
m_autoUpdateTimer.start();
|
||||
} else {
|
||||
m_autoUpdateTimer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ScrcpyManager::setShowToolbar(bool val)
|
||||
{
|
||||
if (m_showToolbar == val)
|
||||
return;
|
||||
m_showToolbar = val;
|
||||
emit showToolbarChanged();
|
||||
}
|
178
QtScrcpy/qml/scrcpymanager.h
Normal file
178
QtScrcpy/qml/scrcpymanager.h
Normal file
|
@ -0,0 +1,178 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <QQmlEngine>
|
||||
#include <QJSEngine>
|
||||
#include <QStringList>
|
||||
#include <qtimer.h>
|
||||
#include "adbprocess.h"
|
||||
#include "scrcpyitem.h"
|
||||
|
||||
class ScrcpyManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(QStringList maxSizeArray READ maxSizeArray CONSTANT FINAL)
|
||||
Q_PROPERTY(QStringList devicesList READ devicesList NOTIFY devicesListChanged)
|
||||
|
||||
Q_PROPERTY(ScrcpyItem* currentDevice READ currentDevice NOTIFY currentDeviceChanged)
|
||||
|
||||
Q_PROPERTY(bool currentlyConnecting READ currentlyConnecting WRITE setCurrentlyConnecting NOTIFY currentlyConnectingChanged)
|
||||
|
||||
Q_PROPERTY(quint32 bitRateNumeric READ bitRateNumeric WRITE setBitRateNumeric NOTIFY bitRateNumericChanged)
|
||||
Q_PROPERTY(QString bitRateUnits READ bitRateUnits WRITE setBitRateUnits NOTIFY bitRateUnitsChanged)
|
||||
|
||||
Q_PROPERTY(int maxSizeIndex READ maxSizeIndex WRITE setMaxSizeIndex NOTIFY maxSizeIndexChanged)
|
||||
Q_PROPERTY(int recordFormatIndex READ recordFormatIndex WRITE setRecordFormatIndex NOTIFY recordFormatIndexChanged)
|
||||
Q_PROPERTY(QString recordPath READ recordPath WRITE setRecordPath NOTIFY recordPathChanged)
|
||||
Q_PROPERTY(int lockOrientationIndex READ lockOrientationIndex WRITE setLockOrientationIndex NOTIFY lockOrientationIndexChanged)
|
||||
|
||||
Q_PROPERTY(bool framelessWindow READ framelessWindow WRITE setFramelessWindow NOTIFY framelessWindowChanged)
|
||||
Q_PROPERTY(bool recordScreen READ recordScreen WRITE setRecordScreen NOTIFY recordScreenChanged)
|
||||
Q_PROPERTY(bool recordBackground READ recordBackground WRITE setRecordBackground NOTIFY recordBackgroundChanged)
|
||||
Q_PROPERTY(bool reverseConnect READ reverseConnect WRITE setReverseConnect NOTIFY reverseConnectChanged)
|
||||
Q_PROPERTY(bool showFPS READ showFPS WRITE setShowFPS NOTIFY showFPSChanged)
|
||||
Q_PROPERTY(bool windowOnTop READ windowOnTop WRITE setWindowOnTop NOTIFY windowOnTopChanged)
|
||||
Q_PROPERTY(bool autoOffScreen READ autoOffScreen WRITE setAutoOffScreen NOTIFY autoOffScreenChanged)
|
||||
Q_PROPERTY(bool keepAlive READ keepAlive WRITE setKeepAlive NOTIFY keepAliveChanged)
|
||||
Q_PROPERTY(bool simpleMode READ simpleMode WRITE setSimpleMode NOTIFY simpleModeChanged)
|
||||
Q_PROPERTY(bool autoUpdateDevice READ autoUpdateDevice WRITE setAutoUpdateDevice NOTIFY autoUpdateDeviceChanged)
|
||||
Q_PROPERTY(bool showToolbar READ showToolbar WRITE setShowToolbar NOTIFY showToolbarChanged)
|
||||
Q_PROPERTY(QStringList availableBitRatesUnits READ availableBitRatesUnits CONSTANT FINAL)
|
||||
Q_PROPERTY(QStringList availableOrientations READ availableOrientations CONSTANT FINAL)
|
||||
|
||||
public:
|
||||
explicit ScrcpyManager(QObject* parent = nullptr);
|
||||
virtual ~ScrcpyManager();
|
||||
|
||||
QStringList devicesList();
|
||||
ScrcpyItem* currentDevice() const;
|
||||
|
||||
bool currentlyConnecting() const;
|
||||
quint32 bitRateNumeric() const;
|
||||
QString bitRateUnits() const;
|
||||
|
||||
int maxSizeIndex() const;
|
||||
int recordFormatIndex() const;
|
||||
QString recordPath() const;
|
||||
int lockOrientationIndex() const;
|
||||
|
||||
bool framelessWindow() const;
|
||||
bool recordScreen() const;
|
||||
bool recordBackground() const;
|
||||
bool reverseConnect() const;
|
||||
bool showFPS() const;
|
||||
bool windowOnTop() const;
|
||||
bool autoOffScreen() const;
|
||||
bool keepAlive() const;
|
||||
bool simpleMode() const;
|
||||
bool autoUpdateDevice() const;
|
||||
bool showToolbar() const;
|
||||
|
||||
QStringList maxSizeArray() const;
|
||||
QStringList availableBitRatesUnits() const;
|
||||
QStringList availableOrientations() const;
|
||||
|
||||
public slots:
|
||||
void listDevices();
|
||||
void connectToDevice(ScrcpyItem* item, const QString &serial);
|
||||
|
||||
void disconnectFromDevice(const QString &serial);
|
||||
void disconnectFromDevice();
|
||||
|
||||
void setCurrentlyConnecting(bool val);
|
||||
void setBitRateNumeric(quint32 val);
|
||||
void setBitRateUnits(const QString &val);
|
||||
|
||||
void setMaxSizeIndex(int val);
|
||||
void setRecordFormatIndex(int val);
|
||||
void setRecordPath(const QString &val);
|
||||
void setLockOrientationIndex(int val);
|
||||
|
||||
void setFramelessWindow(bool val);
|
||||
void setRecordScreen(bool val);
|
||||
void setRecordBackground(bool val);
|
||||
void setReverseConnect(bool val);
|
||||
void setShowFPS(bool val);
|
||||
void setWindowOnTop(bool val);
|
||||
void setAutoOffScreen(bool val);
|
||||
void setKeepAlive(bool val);
|
||||
void setSimpleMode(bool val);
|
||||
void setAutoUpdateDevice(bool val);
|
||||
void setShowToolbar(bool val);
|
||||
|
||||
signals:
|
||||
void devicesListChanged();
|
||||
void currentDeviceChanged();
|
||||
void currentlyConnectingChanged();
|
||||
|
||||
void bitRateNumericChanged();
|
||||
void bitRateUnitsChanged();
|
||||
|
||||
void maxSizeIndexChanged();
|
||||
void recordFormatIndexChanged();
|
||||
void recordPathChanged();
|
||||
void lockOrientationIndexChanged();
|
||||
|
||||
void framelessWindowChanged();
|
||||
void recordScreenChanged();
|
||||
void recordBackgroundChanged();
|
||||
void reverseConnectChanged();
|
||||
void showFPSChanged();
|
||||
void windowOnTopChanged();
|
||||
void autoOffScreenChanged();
|
||||
void keepAliveChanged();
|
||||
void simpleModeChanged();
|
||||
void autoUpdateDeviceChanged();
|
||||
void showToolbarChanged();
|
||||
|
||||
void deviceConnected(const QString &serial);
|
||||
void deviceDisconnected(const QString &serial);
|
||||
|
||||
private:
|
||||
void handleAdbResult(qsc::AdbProcess::ADB_EXEC_RESULT processResult);
|
||||
|
||||
void onDeviceConnected(bool success, const QString &serial, const QString &deviceName, const QSize &size);
|
||||
void onDeviceDisconnected(const QString &serial);
|
||||
bool isAdbBusy();
|
||||
|
||||
const QString &getServerPath();
|
||||
void updateBootConfig(bool toView);
|
||||
|
||||
private:
|
||||
qsc::AdbProcess m_adb;
|
||||
|
||||
QPointer<ScrcpyItem> m_currentItem;
|
||||
QList<QString> m_deviceList;
|
||||
|
||||
bool m_currentlyConnecting = false;
|
||||
|
||||
quint32 m_bitRateNumeric = 0;
|
||||
QString m_bitRateUnits;
|
||||
|
||||
int m_maxSizeIndex = 0;
|
||||
int m_recordFormatIndex = 0;
|
||||
QString m_recordPath;
|
||||
int m_lockOrientationIndex = 0;
|
||||
|
||||
bool m_framelessWindow = false;
|
||||
bool m_recordScreen = false;
|
||||
bool m_recordBackground = false;
|
||||
bool m_reverseConnect = false;
|
||||
bool m_showFPS = false;
|
||||
bool m_windowOnTop = false;
|
||||
bool m_autoOffScreen = false;
|
||||
bool m_keepAlive = false;
|
||||
bool m_simpleMode = false;
|
||||
bool m_autoUpdateDevice = false;
|
||||
bool m_showToolbar = false;
|
||||
|
||||
const QStringList m_maxSizeArray {"640", "720", "1080", "1280", "1920", "original"};
|
||||
const QStringList m_availableBitRatesUnits = {"Mbps", "Kbps"};
|
||||
const QStringList m_availableOrientations = {"no lock", "0", "90", "180", "270"};
|
||||
|
||||
QTimer m_autoUpdateTimer;
|
||||
};
|
|
@ -23,6 +23,7 @@ void XMouseTap::initMouseEventTap() {}
|
|||
void XMouseTap::quitMouseEventTap() {}
|
||||
|
||||
void XMouseTap::enableMouseEventTap(QRect rc, bool enabled) {
|
||||
#if QT_FEATURE_xcb==1
|
||||
if (enabled && rc.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
@ -61,4 +62,8 @@ void XMouseTap::enableMouseEventTap(QRect rc, bool enabled) {
|
|||
XUngrabPointer(display, CurrentTime);
|
||||
}
|
||||
XFlush(display);
|
||||
#else
|
||||
Q_UNUSED(rc);
|
||||
Q_UNUSED(enabled);
|
||||
#endif
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue