PR comments

This commit is contained in:
Brad Davis 2015-12-18 11:19:01 -08:00
parent 5088eec2a2
commit 08a087c5aa
6 changed files with 73 additions and 471 deletions

View file

@ -17,32 +17,23 @@
#include <QtQuick/QQuickItem>
#include <QtWebSockets/QWebSocketServer>
#include <QtWebSockets/QWebSocket>
#include <QtWebChannel/QWebChannel>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <AddressManager.h>
#include <DependencyManager.h>
#include "impl/websocketclientwrapper.h"
#include "impl/websockettransport.h"
#include "OffscreenUi.h"
static QWebSocketServer * webChannelServer { nullptr };
static WebSocketClientWrapper * webChannelClientWrapper { nullptr };
QWebSocketServer* QmlWebWindowClass::_webChannelServer { nullptr };
static QWebChannel webChannel;
static std::once_flag webChannelSetup;
static const uint16_t WEB_CHANNEL_PORT = 51016;
static std::atomic<int> nextWindowId;
void initWebChannelServer() {
std::call_once(webChannelSetup, [] {
webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
webChannelClientWrapper = new WebSocketClientWrapper(webChannelServer);
if (!webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(webChannelClientWrapper, &WebSocketClientWrapper::clientConnected, &webChannel, &QWebChannel::connectTo);
});
}
static const char* const URL_PROPERTY = "source";
static const char* const TITLE_PROPERTY = "title";
static const QRegExp HIFI_URL_PATTERN { "^hifi://" };
void QmlScriptEventBridge::emitWebEvent(const QString& data) {
QMetaObject::invokeMethod(this, "webEventReceived", Qt::QueuedConnection, Q_ARG(QString, data));
@ -50,11 +41,49 @@ void QmlScriptEventBridge::emitWebEvent(const QString& data) {
void QmlScriptEventBridge::emitScriptEvent(const QString& data) {
QMetaObject::invokeMethod(this, "scriptEventReceived", Qt::QueuedConnection,
Q_ARG(int, _webWindow->getWindowId()),
Q_ARG(QString, data)
);
Q_ARG(int, _webWindow->getWindowId()), Q_ARG(QString, data));
}
class QmlWebTransport : public QWebChannelAbstractTransport {
Q_OBJECT
public:
QmlWebTransport(QWebSocket* webSocket) : _webSocket(webSocket) {
// Translate from the websocket layer to the webchannel layer
connect(webSocket, &QWebSocket::textMessageReceived, [this](const QString& message) {
QJsonParseError error;
QJsonDocument document = QJsonDocument::fromJson(message.toUtf8(), &error);
if (error.error || !document.isObject()) {
qWarning() << "Unable to parse incoming JSON message" << message;
return;
}
emit messageReceived(document.object(), this);
});
}
virtual void sendMessage(const QJsonObject &message) override {
// Translate from the webchannel layer to the websocket layer
_webSocket->sendTextMessage(QJsonDocument(message).toJson(QJsonDocument::Compact));
}
private:
QWebSocket* const _webSocket;
};
void QmlWebWindowClass::setupServer() {
if (!_webChannelServer) {
_webChannelServer = new QWebSocketServer("EventBridge Server", QWebSocketServer::NonSecureMode);
if (!_webChannelServer->listen(QHostAddress::LocalHost, WEB_CHANNEL_PORT)) {
qFatal("Failed to open web socket server.");
}
QObject::connect(_webChannelServer, &QWebSocketServer::newConnection, [] {
webChannel.connectTo(new QmlWebTransport(_webChannelServer->nextPendingConnection()));
});
}
}
// Method called by Qt scripts to create a new web window in the overlay
QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
QmlWebWindowClass* retVal { nullptr };
const QString title = context->argument(0).toString();
@ -69,20 +98,19 @@ QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngi
QMetaObject::invokeMethod(DependencyManager::get<OffscreenUi>().data(), "load", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, "QmlWebWindow.qml"),
Q_ARG(std::function<void(QQmlContext*, QObject*)>, [&](QQmlContext* context, QObject* object) {
initWebChannelServer();
setupServer();
retVal = new QmlWebWindowClass(object);
webChannel.registerObject(url.toLower(), retVal);
retVal->setTitle(title);
retVal->setURL(url);
retVal->setSize(width, height);
})
);
}));
connect(engine, &QScriptEngine::destroyed, retVal, &QmlWebWindowClass::deleteLater);
return engine->newQObject(retVal);
}
QmlWebWindowClass::QmlWebWindowClass(QObject* qmlWindow)
: _isToolWindow(false), _windowId(++nextWindowId), _eventBridge(new QmlScriptEventBridge(this)), _qmlWindow(qmlWindow)
: _isToolWindow(false), _windowId(++nextWindowId), _qmlWindow(qmlWindow)
{
qDebug() << "Created window with ID " << _windowId;
Q_ASSERT(_qmlWindow);
@ -94,7 +122,6 @@ void QmlWebWindowClass::handleNavigation(const QString& url) {
DependencyManager::get<AddressManager>()->handleLookupString(url);
}
void QmlWebWindowClass::setVisible(bool visible) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setVisible", Qt::AutoConnection, Q_ARG(bool, visible));
@ -108,6 +135,10 @@ void QmlWebWindowClass::setVisible(bool visible) {
}
}
QQuickItem* QmlWebWindowClass::asQuickItem() const {
return dynamic_cast<QQuickItem*>(_qmlWindow);
}
bool QmlWebWindowClass::isVisible() const {
if (QThread::currentThread() != thread()) {
bool result;
@ -115,7 +146,7 @@ bool QmlWebWindowClass::isVisible() const {
return result;
}
return ((QQuickItem*)_qmlWindow)->isEnabled();
return asQuickItem()->isEnabled();
}
@ -126,7 +157,7 @@ glm::vec2 QmlWebWindowClass::getPosition() const {
return result;
}
return glm::vec2(((QQuickItem*)_qmlWindow)->x(), ((QQuickItem*)_qmlWindow)->y());
return glm::vec2(asQuickItem()->x(), asQuickItem()->y());
}
@ -136,7 +167,7 @@ void QmlWebWindowClass::setPosition(const glm::vec2& position) {
return;
}
((QQuickItem*)_qmlWindow)->setPosition(QPointF(position.x, position.y));
asQuickItem()->setPosition(QPointF(position.x, position.y));
}
void QmlWebWindowClass::setPosition(int x, int y) {
@ -150,7 +181,7 @@ glm::vec2 QmlWebWindowClass::getSize() const {
return result;
}
return glm::vec2(((QQuickItem*)_qmlWindow)->width(), ((QQuickItem*)_qmlWindow)->height());
return glm::vec2(asQuickItem()->width(), asQuickItem()->height());
}
void QmlWebWindowClass::setSize(const glm::vec2& size) {
@ -158,15 +189,13 @@ void QmlWebWindowClass::setSize(const glm::vec2& size) {
QMetaObject::invokeMethod(this, "setSize", Qt::QueuedConnection, Q_ARG(glm::vec2, size));
}
((QQuickItem*)_qmlWindow)->setSize(QSizeF(size.x, size.y));
asQuickItem()->setSize(QSizeF(size.x, size.y));
}
void QmlWebWindowClass::setSize(int width, int height) {
setSize(glm::vec2(width, height));
}
static const char* const URL_PROPERTY = "source";
QString QmlWebWindowClass::getURL() const {
if (QThread::currentThread() != thread()) {
QString result;
@ -183,7 +212,6 @@ void QmlWebWindowClass::setURL(const QString& urlString) {
_qmlWindow->setProperty(URL_PROPERTY, urlString);
}
static const char* const TITLE_PROPERTY = "title";
void QmlWebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
@ -206,146 +234,7 @@ void QmlWebWindowClass::hasClosed() {
}
void QmlWebWindowClass::raise() {
// FIXME
}
#if 0
#include <QtCore/QThread>
#include <QtScript/QScriptEngine>
WebWindowClass::WebWindowClass(const QString& title, const QString& url, int width, int height, bool isToolWindow)
: QObject(NULL), _eventBridge(new ScriptEventBridge(this)), _isToolWindow(isToolWindow) {
/*
if (_isToolWindow) {
ToolWindow* toolWindow = qApp->getToolWindow();
auto dockWidget = new QDockWidget(title, toolWindow);
dockWidget->setFeatures(QDockWidget::DockWidgetMovable);
connect(dockWidget, &QDockWidget::visibilityChanged, this, &WebWindowClass::visibilityChanged);
_webView = new QWebView(dockWidget);
addEventBridgeToWindowObject();
dockWidget->setWidget(_webView);
auto titleWidget = new QWidget(dockWidget);
dockWidget->setTitleBarWidget(titleWidget);
toolWindow->addDockWidget(Qt::TopDockWidgetArea, dockWidget, Qt::Horizontal);
_windowWidget = dockWidget;
} else {
auto dialogWidget = new QDialog(qApp->getWindow(), Qt::Window);
dialogWidget->setWindowTitle(title);
dialogWidget->resize(width, height);
dialogWidget->installEventFilter(this);
connect(dialogWidget, &QDialog::finished, this, &WebWindowClass::hasClosed);
auto layout = new QVBoxLayout(dialogWidget);
layout->setContentsMargins(0, 0, 0, 0);
dialogWidget->setLayout(layout);
_webView = new QWebView(dialogWidget);
layout->addWidget(_webView);
addEventBridgeToWindowObject();
_windowWidget = dialogWidget;
}
auto style = QStyleFactory::create("fusion");
if (style) {
_webView->setStyle(style);
}
_webView->setPage(new DataWebPage());
if (!url.startsWith("http") && !url.startsWith("file://")) {
_webView->setUrl(QUrl::fromLocalFile(url));
} else {
_webView->setUrl(url);
}
connect(this, &WebWindowClass::destroyed, _windowWidget, &QWidget::deleteLater);
connect(_webView->page()->mainFrame(), &QWebFrame::javaScriptWindowObjectCleared,
this, &WebWindowClass::addEventBridgeToWindowObject);
*/
}
void WebWindowClass::hasClosed() {
emit closed();
}
void WebWindowClass::setVisible(bool visible) {
}
QString WebWindowClass::getURL() const {
return QString();
}
void WebWindowClass::setURL(const QString& url) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setURL", Qt::AutoConnection, Q_ARG(QString, url));
return;
}
}
QSizeF WebWindowClass::getSize() const {
QSizeF size;
return size;
}
void WebWindowClass::setSize(const QSizeF& size) {
setSize(size.width(), size.height());
}
void WebWindowClass::setSize(int width, int height) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setSize", Qt::AutoConnection, Q_ARG(int, width), Q_ARG(int, height));
return;
}
}
glm::vec2 WebWindowClass::getPosition() const {
return glm::vec2();
}
void WebWindowClass::setPosition(const glm::vec2& position) {
setPosition(position.x, position.y);
}
void WebWindowClass::setPosition(int x, int y) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setPosition", Qt::AutoConnection, Q_ARG(int, x), Q_ARG(int, y));
return;
}
}
void WebWindowClass::raise() {
}
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
WebWindowClass* retVal { nullptr };
//QString file = context->argument(0).toString();
//QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
// Q_RETURN_ARG(WebWindowClass*, retVal),
// Q_ARG(const QString&, file),
// Q_ARG(QString, context->argument(1).toString()),
// Q_ARG(int, context->argument(2).toInteger()),
// Q_ARG(int, context->argument(3).toInteger()),
// Q_ARG(bool, context->argument(4).toBool()));
//connect(engine, &QScriptEngine::destroyed, retVal, &WebWindowClass::deleteLater);
return engine->newQObject(retVal);
}
void WebWindowClass::setTitle(const QString& title) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setTitle", Qt::AutoConnection, Q_ARG(QString, title));
return;
}
}
#endif
#include "QmlWebWindowClass.moc"

View file

@ -13,10 +13,13 @@
#include <GLMHelpers.h>
#include <QtScript/QScriptValue>
#include <QtQuick/QQuickItem>
#include <QtWebChannel/QWebChannelAbstractTransport>
class QScriptEngine;
class QScriptContext;
class QmlWebWindowClass;
class QWebSocketServer;
class QWebSocket;
class QmlScriptEventBridge : public QObject {
Q_OBJECT
@ -33,6 +36,7 @@ signals:
private:
const QmlWebWindowClass* _webWindow { nullptr };
QWebSocket *_socket { nullptr };
};
// FIXME refactor this class to be a QQuickItem derived type and eliminate the needless wrapping
@ -84,9 +88,13 @@ private slots:
void handleNavigation(const QString& url);
private:
QmlScriptEventBridge* _eventBridge;
bool _isToolWindow { false };
QObject* _qmlWindow;
static void setupServer();
static QWebSocketServer* _webChannelServer;
QQuickItem* asQuickItem() const;
QmlScriptEventBridge* const _eventBridge { new QmlScriptEventBridge(this) };
const bool _isToolWindow;
QObject* const _qmlWindow;
const int _windowId;
};

View file

@ -1,72 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtWebChannel module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "websocketclientwrapper.h"
#include <QtWebSockets/QWebSocketServer>
#include "websockettransport.h"
/*!
\brief Wrapps connected QWebSockets clients in WebSocketTransport objects.
This code is all that is required to connect incoming WebSockets to the WebChannel. Any kind
of remote JavaScript client that supports WebSockets can thus receive messages and access the
published objects.
*/
QT_BEGIN_NAMESPACE
/*!
Construct the client wrapper with the given parent.
All clients connecting to the QWebSocketServer will be automatically wrapped
in WebSocketTransport objects.
*/
WebSocketClientWrapper::WebSocketClientWrapper(QWebSocketServer *server, QObject *parent)
: QObject(parent)
, m_server(server)
{
connect(server, &QWebSocketServer::newConnection,
this, &WebSocketClientWrapper::handleNewConnection);
}
/*!
Wrap an incoming WebSocket connection in a WebSocketTransport object.
*/
void WebSocketClientWrapper::handleNewConnection()
{
emit clientConnected(new WebSocketTransport(m_server->nextPendingConnection()));
}
QT_END_NAMESPACE

View file

@ -1,63 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtWebChannel module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef WEBSOCKETTRANSPORTSERVER_H
#define WEBSOCKETTRANSPORTSERVER_H
#include <QObject>
QT_BEGIN_NAMESPACE
class QWebSocketServer;
class WebSocketTransport;
class WebSocketClientWrapper : public QObject
{
Q_OBJECT
public:
WebSocketClientWrapper(QWebSocketServer *server, QObject *parent = 0);
Q_SIGNALS:
void clientConnected(WebSocketTransport* client);
private Q_SLOTS:
void handleNewConnection();
private:
QWebSocketServer *m_server;
};
QT_END_NAMESPACE
#endif // WEBSOCKETTRANSPORTSERVER_H

View file

@ -1,100 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtWebChannel module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "websockettransport.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QDebug>
#include <QtWebSockets/QWebSocket>
/*!
\brief QWebChannelAbstractSocket implementation that uses a QWebSocket internally.
The transport delegates all messages received over the QWebSocket over its
textMessageReceived signal. Analogously, all calls to sendTextMessage will
be send over the QWebSocket to the remote client.
*/
QT_BEGIN_NAMESPACE
/*!
Construct the transport object and wrap the given socket.
The socket is also set as the parent of the transport object.
*/
WebSocketTransport::WebSocketTransport(QWebSocket *socket)
: QWebChannelAbstractTransport(socket)
, m_socket(socket)
{
connect(socket, &QWebSocket::textMessageReceived,
this, &WebSocketTransport::textMessageReceived);
}
/*!
Destroys the WebSocketTransport.
*/
WebSocketTransport::~WebSocketTransport()
{
}
/*!
Serialize the JSON message and send it as a text message via the WebSocket to the client.
*/
void WebSocketTransport::sendMessage(const QJsonObject &message)
{
QJsonDocument doc(message);
m_socket->sendTextMessage(QString::fromUtf8(doc.toJson(QJsonDocument::Compact)));
}
/*!
Deserialize the stringified JSON messageData and emit messageReceived.
*/
void WebSocketTransport::textMessageReceived(const QString &messageData)
{
QJsonParseError error;
QJsonDocument message = QJsonDocument::fromJson(messageData.toUtf8(), &error);
if (error.error) {
qWarning() << "Failed to parse text message as JSON object:" << messageData
<< "Error is:" << error.errorString();
return;
} else if (!message.isObject()) {
qWarning() << "Received JSON message that is not an object: " << messageData;
return;
}
emit messageReceived(message.object(), this);
}
QT_END_NAMESPACE

View file

@ -1,60 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com>
** Contact: http://www.qt.io/licensing/
**
** This file is part of the QtWebChannel module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL21$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** As a special exception, The Qt Company gives you certain additional
** rights. These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef WEBSOCKETTRANSPORT_H
#define WEBSOCKETTRANSPORT_H
#include <QtWebChannel/QWebChannelAbstractTransport>
QT_BEGIN_NAMESPACE
class QWebSocket;
class WebSocketTransport : public QWebChannelAbstractTransport
{
Q_OBJECT
public:
explicit WebSocketTransport(QWebSocket *socket);
virtual ~WebSocketTransport();
void sendMessage(const QJsonObject &message) Q_DECL_OVERRIDE;
private Q_SLOTS:
void textMessageReceived(const QString &message);
private:
QWebSocket *m_socket;
};
QT_END_NAMESPACE
#endif // WEBSOCKETTRANSPORT_H