From ceeccc721133638630879a46468080332ac36210 Mon Sep 17 00:00:00 2001 From: Jeremy Hu Date: Sun, 17 Mar 2019 15:53:39 +0930 Subject: [PATCH] Start implementation of Remote IO Finished server creation, implemented two commands: listwindow, selectwindow Ideally, all the UI should be possible manipulated by remote IO, a local TCP/IP connection, this could be useful to make a VR front end for Dust3D. --- dust3d.pro | 8 ++- src/documentwindow.cpp | 16 +++-- src/documentwindow.h | 3 + src/main.cpp | 13 ++++ src/remoteioconnection.cpp | 137 +++++++++++++++++++++++++++++++++++++ src/remoteioconnection.h | 39 +++++++++++ src/remoteioserver.cpp | 82 ++++++++++++++++++++++ src/remoteioserver.h | 27 ++++++++ 8 files changed, 319 insertions(+), 6 deletions(-) create mode 100644 src/remoteioconnection.cpp create mode 100644 src/remoteioconnection.h create mode 100644 src/remoteioserver.cpp create mode 100644 src/remoteioserver.h diff --git a/dust3d.pro b/dust3d.pro index 0fc22d09..91bd5572 100644 --- a/dust3d.pro +++ b/dust3d.pro @@ -1,4 +1,4 @@ -QT += core widgets opengl +QT += core widgets opengl network CONFIG += release DEFINES += NDEBUG RESOURCES += resources.qrc @@ -295,6 +295,12 @@ HEADERS += src/cutdocument.h SOURCES += src/cuttemplate.cpp HEADERS += src/cuttemplate.h +SOURCES += src/remoteioserver.cpp +HEADERS += src/remoteioserver.h + +SOURCES += src/remoteioconnection.cpp +HEADERS += src/remoteioconnection.h + SOURCES += src/main.cpp HEADERS += src/version.h diff --git a/src/documentwindow.cpp b/src/documentwindow.cpp index 94de80f7..963a7b49 100644 --- a/src/documentwindow.cpp +++ b/src/documentwindow.cpp @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include #include "documentwindow.h" @@ -45,7 +45,7 @@ int DocumentWindow::m_skeletonRenderWidgetInitialY = DocumentWindow::m_modelRend int DocumentWindow::m_skeletonRenderWidgetInitialSize = DocumentWindow::m_modelRenderWidgetInitialSize; LogBrowser *g_logBrowser = nullptr; -std::set g_documentWindows; +std::map g_documentWindows; QTextBrowser *g_acknowlegementsWidget = nullptr; AboutWidget *g_aboutWidget = nullptr; QTextBrowser *g_contributorsWidget = nullptr; @@ -56,6 +56,11 @@ void outputMessage(QtMsgType type, const QMessageLogContext &context, const QStr g_logBrowser->outputMessage(type, msg, context.file, context.line); } +const std::map &DocumentWindow::documentWindows() +{ + return g_documentWindows; +} + void DocumentWindow::showAcknowlegements() { if (!g_acknowlegementsWidget) { @@ -112,7 +117,7 @@ DocumentWindow::DocumentWindow() : qInstallMessageHandler(&outputMessage); } - g_documentWindows.insert(this); + g_documentWindows.insert({this, QUuid::createUuid()}); m_document = new Document; @@ -1025,8 +1030,8 @@ void DocumentWindow::saveAs() void DocumentWindow::saveAll() { - for (auto &window: g_documentWindows) { - window->save(); + for (auto &it: g_documentWindows) { + it.first->save(); } } @@ -1088,6 +1093,7 @@ void DocumentWindow::initLockButton(QPushButton *button) DocumentWindow::~DocumentWindow() { + emit uninialized(); g_documentWindows.erase(this); } diff --git a/src/documentwindow.h b/src/documentwindow.h index 4f4704e2..f42db8ef 100644 --- a/src/documentwindow.h +++ b/src/documentwindow.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "document.h" #include "modelwidget.h" #include "exportpreviewwidget.h" @@ -22,10 +23,12 @@ class DocumentWindow : public QMainWindow Q_OBJECT signals: void initialized(); + void uninialized(); public: DocumentWindow(); ~DocumentWindow(); static DocumentWindow *createDocumentWindow(); + static const std::map &documentWindows(); static void showAcknowlegements(); static void showContributors(); static void showAbout(); diff --git a/src/main.cpp b/src/main.cpp index ba02902f..36204a46 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,9 +5,11 @@ #include #include #include +#include #include "documentwindow.h" #include "theme.h" #include "version.h" +#include "remoteioserver.h" int main(int argc, char ** argv) { @@ -39,6 +41,8 @@ int main(int argc, char ** argv) //qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #fc6621; border: 1px solid white; }"); QCoreApplication::setApplicationName(APP_NAME); + QCoreApplication::setOrganizationName(APP_COMPANY); + QCoreApplication::setOrganizationDomain(APP_HOMEPAGE_URL); QFont font; font.setWeight(QFont::Light); @@ -50,5 +54,14 @@ int main(int argc, char ** argv) DocumentWindow::createDocumentWindow(); + QSettings settings; + QVariant remoteIoListenPort = settings.value("RemoteIo/ListenPort"); + //if (remoteIoListenPort.isNull()) { + // settings.setValue("RemoteIo/ListenPort", "53309"); + //} + if (!remoteIoListenPort.isNull()) { + new RemoteIoServer(remoteIoListenPort.toInt()); + } + return app.exec(); } diff --git a/src/remoteioconnection.cpp b/src/remoteioconnection.cpp new file mode 100644 index 00000000..6e08a9d3 --- /dev/null +++ b/src/remoteioconnection.cpp @@ -0,0 +1,137 @@ +#include +#include +#include "remoteioconnection.h" + +RemoteIoConnection::RemoteIoConnection(QTcpSocket *tcpSocket) : + m_tcpSocket(tcpSocket) +{ + qDebug() << "Received new remote io connection from:" << m_tcpSocket->peerAddress(); + + connect(m_tcpSocket, &QAbstractSocket::disconnected, this, [&]() { + qDebug() << "Remote connection disconnected from:" << m_tcpSocket->peerAddress(); + }); + connect(m_tcpSocket, SIGNAL(readyRead()),this, SLOT(handleRead())); + + m_commandHandlers["listwindow"] = &RemoteIoConnection::commandListWindow; + m_commandHandlers["selectwindow"] = &RemoteIoConnection::commandSelectWindow; +} + +RemoteIoConnection::~RemoteIoConnection() +{ + qDebug() << "Delete remote io connection"; + + delete m_tcpSocket; +} + +void RemoteIoConnection::handleRead() +{ + m_receiveCache += m_tcpSocket->readAll(); + + for (auto command = nextCommandFromCache(); + !command.isEmpty(); + command = nextCommandFromCache()) { + qDebug() << "Received remote io command:" << command; + int parametersBegin = -1; + QString commandName = nameFromCommand(command, ¶metersBegin); + commandName = commandName.toLower(); + auto findHandler = m_commandHandlers.find(commandName); + if (findHandler == m_commandHandlers.end()) { + qDebug() << "Unrecognized command:" << commandName; + continue; + } + QString errorMessage; + auto response = findHandler->second(this, -1 == parametersBegin ? QByteArray() : command.mid(parametersBegin), &errorMessage); + if (errorMessage.isEmpty()) + m_tcpSocket->write(QByteArray("+OK\r\n").toHex()); + else + m_tcpSocket->write(("-" + errorMessage + "\r\n").toUtf8().toHex()); + m_tcpSocket->write(response.toHex()); + m_tcpSocket->write(0); + } +} + +bool RemoteIoConnection::isWhitespace(char c) +{ + return ' ' == c || '\t' == c || '\r' == c || '\n' == c; +} + +QString RemoteIoConnection::nameFromCommand(const QByteArray &command, int *parametersBegin) +{ + if (nullptr != parametersBegin) { + *parametersBegin = -1; + } + for (int i = 0; i < command.size(); ++i) { + if (isWhitespace(command[i])) { + int nameEnd = i; + if (nullptr != parametersBegin) { + while (isWhitespace(command[i])) + ++i; + if (i < command.size()) + *parametersBegin = i; + } + return command.mid(0, nameEnd); + } + } + return QString(command); +} + +QByteArray RemoteIoConnection::nextCommandFromCache() +{ + for (int i = 0; i < m_receiveCache.size(); ++i) { + if ('\0' == m_receiveCache[i]) { + auto hexBuffer = m_receiveCache.mid(0, i); + if (0 != hexBuffer.size() % 2) { + qDebug() << "Received invalid remote io packet with length:" << hexBuffer.size(); + return QByteArray(); + } + auto command = QByteArray::fromHex(hexBuffer); + m_receiveCache = m_receiveCache.mid(i + 1); + return command; + } + } + return QByteArray(); +} + +QByteArray RemoteIoConnection::commandListWindow(const QByteArray ¶meters, QString *errorMessage) +{ + Q_UNUSED(parameters); + Q_UNUSED(errorMessage); + + QByteArray response; + for (const auto &it: DocumentWindow::documentWindows()) { + response += it.second.toString() + QString(" ") + it.first->windowTitle().replace(" ", "%20") + "\r\n"; + } + return response; +} + +QByteArray RemoteIoConnection::commandSelectWindow(const QByteArray ¶meters, QString *errorMessage) +{ + Q_UNUSED(parameters); + Q_UNUSED(errorMessage); + + QByteArray response; + if (parameters.isEmpty()) { + *errorMessage = "Must specify window id"; + return QByteArray(); + } + QUuid windowId = QUuid(QString(parameters)); + if (windowId.isNull()) { + *errorMessage = "Window id is invalid:" + QString(parameters); + return QByteArray(); + } + for (const auto &it: DocumentWindow::documentWindows()) { + if (it.second == windowId) { + qDebug() << "Remote io select window:" << it.second; + m_currentDocumentWindow = it.first; + connect(m_currentDocumentWindow, &DocumentWindow::uninialized, this, [&]() { + if (sender() == m_currentDocumentWindow) { + m_currentDocumentWindow = nullptr; + qDebug() << "Selected window destroyed"; + } + }); + return QByteArray(); + } + } + *errorMessage = "Window id not found:" + QString(parameters); + return QByteArray(); +} diff --git a/src/remoteioconnection.h b/src/remoteioconnection.h new file mode 100644 index 00000000..c078993d --- /dev/null +++ b/src/remoteioconnection.h @@ -0,0 +1,39 @@ +#ifndef REMOTE_IO_CONNECTION_H +#define REMOTE_IO_CONNECTION_H +#include +#include +#include +#include +#include +#include "documentwindow.h" + +class RemoteIoConnection; +using CommandHandler = std::function; + +class RemoteIoConnection : QObject +{ + Q_OBJECT + +public: + explicit RemoteIoConnection(QTcpSocket *tcpSocket); + ~RemoteIoConnection(); + +private slots: + void handleRead(); + +private: + QTcpSocket *m_tcpSocket = nullptr; + QByteArray m_receiveCache; + DocumentWindow *m_currentDocumentWindow = nullptr; + std::map m_commandHandlers; + + QByteArray nextCommandFromCache(); + QString nameFromCommand(const QByteArray &command, int *parametersBegin=nullptr); + bool isWhitespace(char c); + + QByteArray commandListWindow(const QByteArray ¶meters, QString *errorMessage); + QByteArray commandSelectWindow(const QByteArray ¶meters, QString *errorMessage); +}; + +#endif + diff --git a/src/remoteioserver.cpp b/src/remoteioserver.cpp new file mode 100644 index 00000000..9a7d0970 --- /dev/null +++ b/src/remoteioserver.cpp @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include "remoteioserver.h" +#include "version.h" +#include "remoteioconnection.h" + +RemoteIoServer::RemoteIoServer(int listenPort) : + m_listenPort(listenPort) +{ + QNetworkConfigurationManager manager; + if (manager.capabilities() & QNetworkConfigurationManager::NetworkSessionRequired) { + // Get saved network configuration + QSettings settings; + settings.beginGroup(QLatin1String("QtNetwork")); + const QString id = settings.value(QLatin1String("DefaultNetworkConfiguration")).toString(); + settings.endGroup(); + + // If the saved network configuration is not currently discovered use the system default + QNetworkConfiguration config = manager.configurationFromIdentifier(id); + if ((config.state() & QNetworkConfiguration::Discovered) != + QNetworkConfiguration::Discovered) { + config = manager.defaultConfiguration(); + } + + m_networkSession = new QNetworkSession(config, this); + connect(m_networkSession, &QNetworkSession::opened, this, &RemoteIoServer::sessionOpened); + + m_networkSession->open(); + } else { + sessionOpened(); + } +} + +RemoteIoServer::~RemoteIoServer() +{ + delete m_currentConnection; + delete m_tcpServer; + delete m_networkSession; +} + +void RemoteIoServer::sessionOpened() +{ + // Save the used configuration + if (m_networkSession) { + QNetworkConfiguration config = m_networkSession->configuration(); + QString id; + if (config.type() == QNetworkConfiguration::UserChoice) + id = m_networkSession->sessionProperty(QLatin1String("UserChoiceConfiguration")).toString(); + else + id = config.identifier(); + + QSettings settings; + settings.beginGroup(QLatin1String("QtNetwork")); + settings.setValue(QLatin1String("DefaultNetworkConfiguration"), id); + settings.endGroup(); + } + + m_tcpServer = new QTcpServer(this); + if (!m_tcpServer->listen(QHostAddress::LocalHost, m_listenPort)) { + qDebug() << "Unable to listen on remote io port, error:" << m_tcpServer->errorString(); + return; + } + + connect(m_tcpServer, &QTcpServer::newConnection, this, &RemoteIoServer::handleConnection); + + qDebug() << "Remote io listen on port:" << m_tcpServer->serverPort(); +} + +void RemoteIoServer::handleConnection() +{ + QTcpSocket *clientSocket = m_tcpServer->nextPendingConnection(); + if (nullptr == clientSocket) + return; + + if (nullptr != m_currentConnection) { + delete m_currentConnection; + m_currentConnection = nullptr; + } + m_currentConnection = new RemoteIoConnection(clientSocket); +} diff --git a/src/remoteioserver.h b/src/remoteioserver.h new file mode 100644 index 00000000..ffa8f35a --- /dev/null +++ b/src/remoteioserver.h @@ -0,0 +1,27 @@ +#ifndef DUST3D_REMOTE_IO_SERVER_H +#define DUST3D_REMOTE_IO_SERVER_H +#include +#include +#include +#include "remoteioconnection.h" + +class RemoteIoServer : public QObject +{ + Q_OBJECT + +public: + explicit RemoteIoServer(int listenPort); + ~RemoteIoServer(); + +private slots: + void sessionOpened(); + void handleConnection(); + +private: + int m_listenPort = 0; + QTcpServer *m_tcpServer = nullptr; + QNetworkSession *m_networkSession = nullptr; + RemoteIoConnection *m_currentConnection = nullptr; +}; + +#endif