diff --git a/CMakeLists.txt b/CMakeLists.txt index 7762e2a..ba0c4a6 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ cmake_minimum_required(VERSION 3.19 FATAL_ERROR) project(all) -add_subdirectory(QtScrcpy) +add_subdirectory(QtScrcpy) \ No newline at end of file diff --git a/QtScrcpy/CMakeLists.txt b/QtScrcpy/CMakeLists.txt index caceac7..5de75a1 100755 --- a/QtScrcpy/CMakeLists.txt +++ b/QtScrcpy/CMakeLists.txt @@ -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() diff --git a/QtScrcpy/TestQml/CMakeLists.txt b/QtScrcpy/TestQml/CMakeLists.txt new file mode 100644 index 0000000..0bb8cc3 --- /dev/null +++ b/QtScrcpy/TestQml/CMakeLists.txt @@ -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" +) diff --git a/QtScrcpy/TestQml/Main.qml b/QtScrcpy/TestQml/Main.qml new file mode 100644 index 0000000..7a4fc7a --- /dev/null +++ b/QtScrcpy/TestQml/Main.qml @@ -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() + } +} diff --git a/QtScrcpy/TestQml/main.cpp b/QtScrcpy/TestQml/main.cpp new file mode 100644 index 0000000..0805221 --- /dev/null +++ b/QtScrcpy/TestQml/main.cpp @@ -0,0 +1,13 @@ +#include +#include + +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(); +} diff --git a/QtScrcpy/qml/scrcpyitem.cpp b/QtScrcpy/qml/scrcpyitem.cpp new file mode 100644 index 0000000..2f05ec4 --- /dev/null +++ b/QtScrcpy/qml/scrcpyitem.cpp @@ -0,0 +1,524 @@ +#include "scrcpyitem.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(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 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(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); +} diff --git a/QtScrcpy/qml/scrcpyitem.h b/QtScrcpy/qml/scrcpyitem.h new file mode 100644 index 0000000..9215057 --- /dev/null +++ b/QtScrcpy/qml/scrcpyitem.h @@ -0,0 +1,133 @@ +#ifndef SCRCPYITEM_H +#define SCRCPYITEM_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 diff --git a/QtScrcpy/qml/scrcpymanager.cpp b/QtScrcpy/qml/scrcpymanager.cpp new file mode 100644 index 0000000..2410dc4 --- /dev/null +++ b/QtScrcpy/qml/scrcpymanager.cpp @@ -0,0 +1,520 @@ +#include "scrcpymanager.h" +#include +#include +#include +#include +#include +#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(videoSizeStr.toUShort()); + params.maxSize = videoSize; + + if (bitRateUnits() == "Mbps") { + params.bitRate = bitRateNumeric() * 1000000; + } else { + params.bitRate = bitRateNumeric() * 1000; + } + + params.maxFps = static_cast(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(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(); +} diff --git a/QtScrcpy/qml/scrcpymanager.h b/QtScrcpy/qml/scrcpymanager.h new file mode 100644 index 0000000..ffbfc46 --- /dev/null +++ b/QtScrcpy/qml/scrcpymanager.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#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 m_currentItem; + QList 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; +}; diff --git a/QtScrcpy/util/mousetap/xmousetap.cpp b/QtScrcpy/util/mousetap/xmousetap.cpp index a5fa561..a455f16 100644 --- a/QtScrcpy/util/mousetap/xmousetap.cpp +++ b/QtScrcpy/util/mousetap/xmousetap.cpp @@ -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 }