diff --git a/src/QtScrcpy.pro b/src/QtScrcpy.pro new file mode 100644 index 0000000..98eae6d --- /dev/null +++ b/src/QtScrcpy.pro @@ -0,0 +1,39 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2018-10-07T12:36:10 +# +#------------------------------------------------- + +QT += core gui +QT += network + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = QtScrcpy +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + + +SOURCES += \ + main.cpp \ + dialog.cpp \ + adbprocess.cpp \ + server.cpp + +HEADERS += \ + dialog.h \ + adbprocess.h \ + server.h + +FORMS += \ + dialog.ui diff --git a/src/QtScrcpy.pro.user b/src/QtScrcpy.pro.user new file mode 100644 index 0000000..d29803c --- /dev/null +++ b/src/QtScrcpy.pro.user @@ -0,0 +1,318 @@ + + + + + + EnvironmentId + {49c3991c-71bb-4f46-826d-1d1cf5cba2d4} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.9.6 MSVC2015 32bit + Desktop Qt 5.9.6 MSVC2015 32bit + qt.596.win32_msvc2015_kit + 0 + 0 + 0 + + G:/QT/code/build-QtScrcpy-Desktop_Qt_5_9_6_MSVC2015_32bit-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + G:/QT/code/build-QtScrcpy-Desktop_Qt_5_9_6_MSVC2015_32bit-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + G:/QT/code/build-QtScrcpy-Desktop_Qt_5_9_6_MSVC2015_32bit-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + 部署 + + ProjectExplorer.BuildSteps.Deploy + + 1 + 部署设置 + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + QtScrcpy + + Qt4ProjectManager.Qt4RunConfiguration:G:/QT/code/QtScrcpy/QtScrcpy.pro + true + + QtScrcpy.pro + false + + G:/QT/code/build-QtScrcpy-Desktop_Qt_5_9_6_MSVC2015_32bit-Debug + 3768 + false + true + false + false + true + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 18 + + + Version + 18 + + diff --git a/src/adbprocess.cpp b/src/adbprocess.cpp new file mode 100644 index 0000000..036a38a --- /dev/null +++ b/src/adbprocess.cpp @@ -0,0 +1,160 @@ +#include "adbprocess.h" + +#include +#include +#include + +QString AdbProcess::s_adbPath = ""; + +AdbProcess::AdbProcess(QObject *parent) + : QProcess(parent) +{ + initSignals(); +} + +AdbProcess::~AdbProcess() +{ + if (isRuning()) { + close(); + } +} + +const QString& AdbProcess::getAdbPath() +{ + if (s_adbPath.isEmpty()) { + s_adbPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_ADB_PATH")); + if (s_adbPath.isEmpty()) { + s_adbPath = "adb"; + } + } + return s_adbPath; +} + +void AdbProcess::initSignals() +{ + // aboutToQuit not exit event loop, so deletelater is ok + //connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &AdbProcess::deleteLater); + + connect(this, static_cast(&QProcess::finished), this, + [this](int exitCode, QProcess::ExitStatus exitStatus){ + if (NormalExit == exitStatus && 0 == exitCode) { + emit adbProcessResult(AER_SUCCESS); + } else { + emit adbProcessResult(AER_ERROR_CMD); + } + + qDebug() << ">>>>>>>>" << __FUNCTION__; + qDebug() << "adb return " << exitCode << "exit status " << exitStatus; + }); + + connect(this, &QProcess::errorOccurred, this, + [this](QProcess::ProcessError error){ + if (QProcess::FailedToStart == error) { + emit adbProcessResult(AER_ERROR_MISSING_BINARY); + } else { + emit adbProcessResult(AER_ERROR_START); + QString err = QString("qprocess start error:%1 %2").arg(program()).arg(arguments().join(" ")); + qCritical(err.toStdString().c_str()); + } + }); + + connect(this, &QProcess::readyReadStandardError, this, + [this](){ + qDebug() << ">>>>>>>>" << __FUNCTION__; + qDebug() << QString::fromLocal8Bit(readAllStandardError()); + }); + + connect(this, &QProcess::readyReadStandardOutput, this, + [this](){ + qDebug() << ">>>>>>>>" << __FUNCTION__; + qDebug() << QString::fromLocal8Bit(readAllStandardOutput()); + }); + + connect(this, &QProcess::started, this, + [this](){ + + }); +} + +void AdbProcess::execute(const QString& serial, const QStringList& args) +{ + QStringList adbArgs; + if (!serial.isEmpty()) { + adbArgs << "-s" << serial; + } + adbArgs << args; + start(getAdbPath(), adbArgs); + //start("C:\\Users\\Barry\\Desktop\\sockettool.exe", Q_NULLPTR); +} + +bool AdbProcess::isRuning() +{ + if (QProcess::NotRunning == state()) { + return false; + } else { + return true; + } +} + +void AdbProcess::forward(const QString& serial, quint16 localPort, const QString& deviceSocketName) +{ + QStringList adbArgs; + adbArgs << "forward"; + adbArgs << QString("tcp:%1").arg(localPort); + adbArgs << QString("localabstract:%1").arg(deviceSocketName); + execute(serial, adbArgs); +} + +void AdbProcess::forwardRemove(const QString& serial, quint16 localPort) +{ + QStringList adbArgs; + adbArgs << "forward"; + adbArgs << "--remove"; + adbArgs << QString("tcp:%1").arg(localPort); + execute(serial, adbArgs); +} + +void AdbProcess::reverse(const QString& serial, const QString& deviceSocketName, quint16 localPort) +{ + QStringList adbArgs; + adbArgs << "reverse"; + adbArgs << QString("localabstract:%1").arg(deviceSocketName); + adbArgs << QString("tcp:%1").arg(localPort); + execute(serial, adbArgs); +} + +void AdbProcess::reverseRemove(const QString& serial, const QString& deviceSocketName) +{ + QStringList adbArgs; + adbArgs << "reverse"; + adbArgs << "--remove"; + adbArgs << QString("localabstract:%1").arg(deviceSocketName); + execute(serial, adbArgs); +} + +void AdbProcess::push(const QString& serial, const QString& local, const QString& remote) +{ + QStringList adbArgs; + adbArgs << "push"; + adbArgs << local; + adbArgs << remote; + execute(serial, adbArgs); +} + +void AdbProcess::install(const QString& serial, const QString& local) +{ + QStringList adbArgs; + adbArgs << "install"; + adbArgs << "-r"; + adbArgs << local; + execute(serial, adbArgs); +} + +void AdbProcess::removePath(const QString& serial, const QString& path) +{ + QStringList adbArgs; + adbArgs << "shell"; + adbArgs << "rm"; + adbArgs << path; + execute(serial, adbArgs); +} diff --git a/src/adbprocess.h b/src/adbprocess.h new file mode 100644 index 0000000..698dd1a --- /dev/null +++ b/src/adbprocess.h @@ -0,0 +1,42 @@ +#ifndef ADBPROCESS_H +#define ADBPROCESS_H + +#include + +class AdbProcess : public QProcess +{ + Q_OBJECT + +public: + enum ADB_EXEC_RESULT { + AER_SUCCESS, // 执行成功 + AER_ERROR_START, // 启动失败 + AER_ERROR_CMD, // 命令执行失败 + AER_ERROR_MISSING_BINARY, // 找不到文件 + }; + + explicit AdbProcess(QObject *parent = nullptr); + ~AdbProcess(); + + void execute(const QString& serial, const QStringList& args); + void forward(const QString& serial, quint16 localPort, const QString& deviceSocketName); + void forwardRemove(const QString& serial, quint16 localPort); + void reverse(const QString& serial, const QString& deviceSocketName, quint16 localPort); + void reverseRemove(const QString& serial, const QString& deviceSocketName); + void push(const QString& serial, const QString& local, const QString& remote); + void install(const QString& serial, const QString& local); + void removePath(const QString& serial, const QString& path); + bool isRuning(); + + static const QString& getAdbPath(); + +signals: + void adbProcessResult(ADB_EXEC_RESULT processResult); + +private: + void initSignals(); + + static QString s_adbPath; +}; + +#endif // ADBPROCESS_H diff --git a/src/dialog.cpp b/src/dialog.cpp new file mode 100644 index 0000000..1a2c7db --- /dev/null +++ b/src/dialog.cpp @@ -0,0 +1,24 @@ +#include "dialog.h" +#include "ui_dialog.h" +#include "adbprocess.h" + +Dialog::Dialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::Dialog) +{ + ui->setupUi(this); +} + +Dialog::~Dialog() +{ + delete ui; +} + +void Dialog::on_adbProcess_clicked() +{ + AdbProcess* adb = new AdbProcess(); + connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult){ + sender()->deleteLater(); + }); + adb->execute("", QStringList() << "devices"); +} diff --git a/src/dialog.h b/src/dialog.h new file mode 100644 index 0000000..4152a2c --- /dev/null +++ b/src/dialog.h @@ -0,0 +1,25 @@ +#ifndef DIALOG_H +#define DIALOG_H + +#include + +namespace Ui { +class Dialog; +} + +class Dialog : public QDialog +{ + Q_OBJECT + +public: + explicit Dialog(QWidget *parent = 0); + ~Dialog(); + +private slots: + void on_adbProcess_clicked(); + +private: + Ui::Dialog *ui; +}; + +#endif // DIALOG_H diff --git a/src/dialog.ui b/src/dialog.ui new file mode 100644 index 0000000..7f52460 --- /dev/null +++ b/src/dialog.ui @@ -0,0 +1,33 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + 30 + 20 + 93 + 28 + + + + PushButton + + + + + + + diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..99e9f01 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,17 @@ +#include "dialog.h" + +#include +#include + + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + qputenv("QTSCRCPY_ADB_PATH", "C:\\Users\\Barry\\Desktop\\scrcpy-win64\\adb.exe"); + + Dialog w; + w.show(); + + return a.exec(); +} diff --git a/src/server.cpp b/src/server.cpp new file mode 100644 index 0000000..f4172de --- /dev/null +++ b/src/server.cpp @@ -0,0 +1,221 @@ +#include + +#include "server.h" + +#define DEVICE_SERVER_PATH "/data/local/tmp/scrcpy-server.jar" +#define SOCKET_NAME "qtscrcpy" + +Server::Server(QObject *parent) : QObject(parent) +{ + connect(&m_workProcess, &AdbProcess::adbProcessResult, this, &Server::onWorkProcessResult); +} + +const QString& Server::getServerPath() +{ + if (m_serverPath.isEmpty()) { + m_serverPath = QString::fromLocal8Bit(qgetenv("QTSCRCPY_SERVER_PATH")); + if (m_serverPath.isEmpty()) { + m_serverPath = "scrcpy-server.jar"; + } + } + return m_serverPath; +} + +bool Server::pushServer() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.push(m_serial, getServerPath(), DEVICE_SERVER_PATH); + return true; +} + +bool Server::removeServer() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.removePath(m_serial, DEVICE_SERVER_PATH); + return true; +} + +bool Server::enableTunnelReverse() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.reverse(m_serial, SOCKET_NAME, m_localPort); + return true; +} + +bool Server::disableTunnelReverse() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.reverseRemove(m_serial, SOCKET_NAME); + return true; +} + +bool Server::enableTunnelForward() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.forward(m_serial, m_localPort, SOCKET_NAME); + return true; +} +bool Server::disableTunnelForward() +{ + if (m_workProcess.isRuning()) { + m_workProcess.kill(); + } + m_workProcess.forwardRemove(m_serial, m_localPort); + return true; +} + +bool Server::execute() +{ + AdbProcess* adb = new AdbProcess(); + if (!adb) { + return false; + } + QStringList args; + args << "shell"; + args << QString("CLASSPATH=%1").arg(DEVICE_SERVER_PATH); + args << "app_process"; + args << "/"; // unused; + args << "com.genymobile.scrcpy.Server"; + args << QString::number(m_maxSize); + args << QString::number(m_bitRate); + args << (m_tunnelForward ? "true" : "false"); + args << (m_crop.isEmpty() ? "" : m_crop); + + connect(adb, &AdbProcess::adbProcessResult, this, [this](AdbProcess::ADB_EXEC_RESULT processResult){ + if (AdbProcess::AER_SUCCESS == processResult) { + + } + sender()->deleteLater(); + }); + adb->execute(m_serial, args); + return true; +} + +bool Server::start(const QString& serial, quint16 localPort, quint16 maxSize, quint32 bitRate, const QString& crop) +{ + if (serial.isEmpty()) { + return false; + } + + m_localPort = localPort; + m_maxSize = maxSize; + m_bitRate = bitRate; + m_crop = crop; + + m_serverStartStep = SSS_PUSH; + return startServerByStep(); +} + +bool Server::startServerByStep() +{ + // push, enable tunnel et start the server + if (SSS_NULL != m_serverStartStep) { + switch (m_serverStartStep) { + case SSS_PUSH: + return pushServer(); + case SSS_ENABLE_TUNNEL_REVERSE: + return enableTunnelReverse(); + break; + case SSS_ENABLE_TUNNEL_FORWARD: + return enableTunnelForward(); + break; + case SSS_EXECUTE_SERVER: + // if "adb reverse" does not work (e.g. over "adb connect"), it fallbacks to + // "adb forward", so the app socket is the client + if (m_tunnelForward) { + // At the application level, the device part is "the server" because it + // serves video stream and control. However, at the network level, the + // client listens and the server connects to the client. That way, the + // client can listen before starting the server app, so there is no need to + // try to connect until the server socket is listening on the device. + m_serverSocket.setMaxPendingConnections(1); + if (!m_serverSocket.listen(QHostAddress::LocalHost, m_localPort)) { + qCritical(QString("Could not listen on port %1").arg(m_localPort).toStdString().c_str()); + m_serverStartStep = SSS_NULL; + if (m_tunnelForward) { + disableTunnelForward(); + } else { + disableTunnelReverse(); + } + emit serverStartResult(false); + return false; + } + } + // server will connect to our server socket + return execute(); + break; + default: + break; + } + } + return true; +} + +void Server::onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult) +{ + if (SSS_NULL != m_serverStartStep) { + switch (m_serverStartStep) { + case SSS_PUSH: + if (AdbProcess::AER_SUCCESS != processResult) { + qCritical("adb push"); + m_serverStartStep = SSS_NULL; + emit serverStartResult(false); + } else { + m_serverCopiedToDevice = true; + m_serverStartStep = SSS_ENABLE_TUNNEL_REVERSE; + startServerByStep(); + } + break; + case SSS_ENABLE_TUNNEL_REVERSE: + if (AdbProcess::AER_SUCCESS != processResult) { + qCritical("adb reverse"); + m_tunnelForward = true; + m_serverStartStep = SSS_ENABLE_TUNNEL_FORWARD; + startServerByStep(); + } else { + m_serverStartStep = SSS_EXECUTE_SERVER; + startServerByStep(); + } + break; + case SSS_ENABLE_TUNNEL_FORWARD: + if (AdbProcess::AER_SUCCESS != processResult) { + qCritical("adb forward"); + m_serverStartStep = SSS_NULL; + emit serverStartResult(false); + } else { + m_serverStartStep = SSS_EXECUTE_SERVER; + startServerByStep(); + } + break; + case SSS_EXECUTE_SERVER: + if (AdbProcess::AER_SUCCESS != processResult) { + if (!m_tunnelForward) { + m_serverSocket.close(); + disableTunnelReverse(); + } else { + disableTunnelForward(); + } + qCritical("adb shell start server failed"); + m_serverStartStep = SSS_NULL; + emit serverStartResult(false); + } else { + m_serverStartStep = SSS_NULL; + m_tunnelEnabled = true; + emit serverStartResult(true); + } + break; + default: + break; + } + } +} diff --git a/src/server.h b/src/server.h new file mode 100644 index 0000000..7c77bec --- /dev/null +++ b/src/server.h @@ -0,0 +1,62 @@ +#ifndef SERVER_H +#define SERVER_H + +#include +#include +#include + +#include "adbprocess.h" + +class Server : public QObject +{ + Q_OBJECT + + enum SERVER_START_STEP { + SSS_NULL, + SSS_PUSH, + SSS_ENABLE_TUNNEL_REVERSE, + SSS_ENABLE_TUNNEL_FORWARD, + SSS_EXECUTE_SERVER, + }; +public: + explicit Server(QObject *parent = nullptr); + + bool start(const QString& serial, quint16 localPort, quint16 maxSize, quint32 bitRate, const QString& crop); + + bool pushServer(); + bool removeServer(); + bool enableTunnelReverse(); + bool disableTunnelReverse(); + bool enableTunnelForward(); + bool disableTunnelForward(); + + bool execute(); + +signals: + void serverStartResult(bool success); + +public slots: + void onWorkProcessResult(AdbProcess::ADB_EXEC_RESULT processResult); + +private: + const QString& getServerPath(); + bool startServerByStep(); + + QString m_serverPath = ""; + AdbProcess m_workProcess; + QString m_serial = ""; + AdbProcess m_serverProcess; + QTcpServer m_serverSocket; // only used if !tunnel_forward + QTcpSocket m_deviceSocket; + quint16 m_localPort = 0; + bool m_tunnelEnabled = false; + bool m_tunnelForward = false; // use "adb forward" instead of "adb reverse" + bool m_serverCopiedToDevice = false; + quint16 m_maxSize = 0; + quint32 m_bitRate = 0; + QString m_crop = ""; + + SERVER_START_STEP m_serverStartStep = SSS_NULL; +}; + +#endif // SERVER_H