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.
master
Jeremy Hu 2019-03-17 15:53:39 +09:30
parent a1249f5fcb
commit ceeccc7211
8 changed files with 319 additions and 6 deletions

View File

@ -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

View File

@ -12,7 +12,7 @@
#include <QMenuBar>
#include <QPointer>
#include <QApplication>
#include <set>
#include <map>
#include <QDesktopServices>
#include <QDockWidget>
#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<DocumentWindow *> g_documentWindows;
std::map<DocumentWindow *, QUuid> 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 *, QUuid> &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);
}

View File

@ -7,6 +7,7 @@
#include <QMenu>
#include <QAction>
#include <QTextBrowser>
#include <map>
#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<DocumentWindow *, QUuid> &documentWindows();
static void showAcknowlegements();
static void showContributors();
static void showAbout();

View File

@ -5,9 +5,11 @@
#include <QDebug>
#include <QtGlobal>
#include <QSurfaceFormat>
#include <QSettings>
#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();
}

137
src/remoteioconnection.cpp Normal file
View File

@ -0,0 +1,137 @@
#include <QAbstractSocket>
#include <QHostAddress>
#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, &parametersBegin);
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 &parameters, 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 &parameters, 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();
}

39
src/remoteioconnection.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef REMOTE_IO_CONNECTION_H
#define REMOTE_IO_CONNECTION_H
#include <QObject>
#include <QTcpSocket>
#include <vector>
#include <QByteArray>
#include <map>
#include "documentwindow.h"
class RemoteIoConnection;
using CommandHandler = std::function<QByteArray(RemoteIoConnection *, const QByteArray&, QString *)>;
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<QString, CommandHandler> m_commandHandlers;
QByteArray nextCommandFromCache();
QString nameFromCommand(const QByteArray &command, int *parametersBegin=nullptr);
bool isWhitespace(char c);
QByteArray commandListWindow(const QByteArray &parameters, QString *errorMessage);
QByteArray commandSelectWindow(const QByteArray &parameters, QString *errorMessage);
};
#endif

82
src/remoteioserver.cpp Normal file
View File

@ -0,0 +1,82 @@
#include <QNetworkConfigurationManager>
#include <QSettings>
#include <QAbstractSocket>
#include <QTcpSocket>
#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);
}

27
src/remoteioserver.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef DUST3D_REMOTE_IO_SERVER_H
#define DUST3D_REMOTE_IO_SERVER_H
#include <QObject>
#include <QTcpServer>
#include <QNetworkSession>
#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