diff --git a/deflect/qt/CMakeLists.txt b/deflect/qt/CMakeLists.txt index dbf652f..e3a110a 100644 --- a/deflect/qt/CMakeLists.txt +++ b/deflect/qt/CMakeLists.txt @@ -5,16 +5,19 @@ set(DEFLECTQT_HEADERS EventReceiver.h + helpers.h QmlGestures.h QmlStreamerImpl.h ) set(DEFLECTQT_PUBLIC_HEADERS + OffscreenQuickView.h QmlStreamer.h QuickRenderer.h TouchInjector.h ) set(DEFLECTQT_SOURCES EventReceiver.cpp + OffscreenQuickView.cpp QmlStreamer.cpp QmlStreamerImpl.cpp QuickRenderer.cpp diff --git a/deflect/qt/EventReceiver.cpp b/deflect/qt/EventReceiver.cpp index 8f4a9d6..74a0855 100644 --- a/deflect/qt/EventReceiver.cpp +++ b/deflect/qt/EventReceiver.cpp @@ -70,7 +70,7 @@ inline QPointF _pos( const Event& deflectEvent ) return QPointF{ deflectEvent.mouseX, deflectEvent.mouseY }; } -void EventReceiver::_onEvent( int socket ) +void EventReceiver::_onEvent( const int socket ) { if( socket != _stream.getDescriptor( )) return; @@ -82,9 +82,8 @@ void EventReceiver::_onEvent( int socket ) switch( deflectEvent.type ) { case Event::EVT_CLOSE: - _notifier->setEnabled( false ); - emit closed(); - break; + _stop(); + return; case Event::EVT_PRESS: emit pressed( _pos( deflectEvent )); break; @@ -134,7 +133,18 @@ void EventReceiver::_onEvent( int socket ) break; } } + + if( !_stream.isConnected( )) + _stop(); +} + +void EventReceiver::_stop() +{ + _notifier->setEnabled( false ); + _timer->stop(); + emit closed(); } + } } diff --git a/deflect/qt/EventReceiver.h b/deflect/qt/EventReceiver.h index 0c83a4f..f27c9bb 100644 --- a/deflect/qt/EventReceiver.h +++ b/deflect/qt/EventReceiver.h @@ -38,8 +38,8 @@ /* or implied, of The University of Texas at Austin. */ /*********************************************************************/ -#ifndef EVENTRECEIVER_H -#define EVENTRECEIVER_H +#ifndef DELFECT_QT_EVENTRECEIVER_H +#define DELFECT_QT_EVENTRECEIVER_H #include #include @@ -82,13 +82,13 @@ class EventReceiver : public QObject void touchPointUpdated( int id, QPointF position ); void touchPointRemoved( int id, QPointF position ); -private slots: - void _onEvent( int socket ); - private: Stream& _stream; std::unique_ptr< QSocketNotifier > _notifier; std::unique_ptr< QTimer > _timer; + + void _onEvent( int socket ); + void _stop(); }; } diff --git a/deflect/qt/OffscreenQuickView.cpp b/deflect/qt/OffscreenQuickView.cpp new file mode 100644 index 0000000..d1a8a11 --- /dev/null +++ b/deflect/qt/OffscreenQuickView.cpp @@ -0,0 +1,248 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Daniel Nachbaur */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#include "OffscreenQuickView.h" + +#include "QuickRenderer.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace deflect +{ +namespace qt +{ + +OffscreenQuickView::OffscreenQuickView( std::unique_ptr + renderControl, const RenderMode mode ) + : QQuickWindow{ renderControl.get() } + , _renderControl{ std::move( renderControl ) } + , _mode{ mode } + , _qmlEngine{ new QQmlEngine } + , _qmlComponent{ new QQmlComponent{ _qmlEngine.get() }} +{ + if( !_qmlEngine->incubationController( )) + _qmlEngine->setIncubationController( incubationController( )); + + // remote URL to QML components are loaded asynchronously + connect( _qmlComponent.get(), &QQmlComponent::statusChanged, + this, &OffscreenQuickView::_setupRootItem ); + + // Now hook up the signals. For simplicity we don't differentiate between + // renderRequested (only render is needed, no sync) and sceneChanged (polish + // and sync is needed too). + connect( _renderControl.get(), &QQuickRenderControl::renderRequested, + this, &OffscreenQuickView::_requestRender ); + connect( _renderControl.get(), &QQuickRenderControl::sceneChanged, + this, &OffscreenQuickView::_requestRender ); +} + +OffscreenQuickView::~OffscreenQuickView() +{ + killTimer( _renderTimer ); + _renderTimer = 0; + killTimer( _stopRenderingDelayTimer ); + _stopRenderingDelayTimer = 0; + + _quickRenderer->stop(); + if( _quickRendererThread ) + { + _quickRendererThread->quit(); + _quickRendererThread->wait(); + } + + // delete first to free scenegraph resources for following destructions + _renderControl.reset(); +} + +std::future OffscreenQuickView::load( const QUrl& url ) +{ + _qmlComponent->loadUrl( url ); + return _loadPromise.get_future(); +} + +QQuickItem* OffscreenQuickView::getRootItem() const +{ + return _rootItem.get(); +} + +QQmlEngine* OffscreenQuickView::getEngine() const +{ + return _qmlEngine.get(); +} + +QQmlContext* OffscreenQuickView::getRootContext() const +{ + return _qmlEngine->rootContext(); +} + +void OffscreenQuickView::timerEvent( QTimerEvent* e ) +{ + if( e->timerId() == _renderTimer ) + _render(); + else if( e->timerId() == _stopRenderingDelayTimer ) + { + killTimer( _renderTimer ); + killTimer( _stopRenderingDelayTimer ); + _renderTimer = 0; + _stopRenderingDelayTimer = 0; + } +} + +void OffscreenQuickView::_setupRootItem() +{ + disconnect( _qmlComponent.get(), &QQmlComponent::statusChanged, + this, &OffscreenQuickView::_setupRootItem ); + + if( _qmlComponent->isError( )) + { + for( const auto& error : _qmlComponent->errors( )) + qWarning() << error.url() << error.line() << error; + _loadPromise.set_value( false ); + } + + QObject* rootObject = _qmlComponent->create(); + if( _qmlComponent->isError( )) + { + for( const auto& error : _qmlComponent->errors( )) + qWarning() << error.url() << error.line() << error; + _loadPromise.set_value( false ); + return; + } + + _rootItem.reset( qobject_cast< QQuickItem* >( rootObject )); + if( !_rootItem ) + { + delete rootObject; + _loadPromise.set_value( false ); + return; + } + _rootItem->setParentItem( contentItem( )); + resize( _rootItem->width(), _rootItem->height( )); + + // emulate QQuickView::ResizeMode::SizeRootObjectToView + connect( this, &QQuickWindow::widthChanged, + _rootItem.get(), &QQuickItem::setWidth ); + connect( this, &QQuickWindow::heightChanged, + _rootItem.get(), &QQuickItem::setHeight ); + + // also bind view to root object, to allow programmatic resize of rootItem + connect( _rootItem.get(), &QQuickItem::widthChanged, + [this]() { setWidth( _rootItem->width( )); } ); + connect( _rootItem.get(), &QQuickItem::heightChanged, + [this]() { setHeight( _rootItem->height( )); } ); + + _loadPromise.set_value( true ); +} + +void OffscreenQuickView::_requestRender() +{ + killTimer( _stopRenderingDelayTimer ); + _stopRenderingDelayTimer = 0; + + if( _renderTimer == 0 ) + _renderTimer = startTimer( 5, Qt::PreciseTimer ); +} + +void OffscreenQuickView::_initRenderer() +{ + switch( _mode ) + { + case RenderMode::MULTITHREADED: +#ifdef DEFLECTQT_MULTITHREADED + _quickRenderer.reset( new QuickRenderer{ *this, *_renderControl, true, + RenderTarget::FBO } ); + + _quickRendererThread.reset( new QThread ); + _quickRendererThread->setObjectName( "Render" ); + + // Call required to make QtGraphicalEffects work in the initial scene. + _renderControl->prepareThread( _quickRendererThread.get( )); + _quickRenderer->moveToThread( _quickRendererThread.get( )); + _quickRendererThread->start(); +#else + throw std::runtime_error( "This version of deflect does not support " + "multithreaded rendering." ); +#endif + break; + case RenderMode::SINGLETHREADED: + _quickRenderer.reset( new QuickRenderer{ *this, *_renderControl, + false, RenderTarget::FBO } ); + break; + case RenderMode::DISABLED: + _quickRenderer.reset( new QuickRenderer{ *this, *_renderControl, + false, RenderTarget::NONE } ); + break; + } + + connect( _quickRenderer.get(), &QuickRenderer::afterRender, + this, &OffscreenQuickView::_afterRender, Qt::DirectConnection ); + + _quickRenderer->init(); +} + +void OffscreenQuickView::_render() +{ + // Initialize the render control and our OpenGL resources. Do this as late + // as possible to use the proper size reported by the rootItem. + if( !_quickRenderer ) + _initRenderer(); + + _renderControl->polishItems(); + _quickRenderer->render(); + + if( _stopRenderingDelayTimer == 0 ) + _stopRenderingDelayTimer = startTimer( 5000 /*ms*/ ); +} + +void OffscreenQuickView::_afterRender() +{ + // Called directly by the render thread just after the rendering is done. + // The fbo can be safely assumed to be valid when this function executes. + emit afterRender( _quickRenderer->fbo()->toImage( )); +} + +} +} diff --git a/deflect/qt/OffscreenQuickView.h b/deflect/qt/OffscreenQuickView.h new file mode 100644 index 0000000..b45fa3b --- /dev/null +++ b/deflect/qt/OffscreenQuickView.h @@ -0,0 +1,141 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Daniel Nachbaur */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef DELFECT_QT_OFFSCREENQUICKVIEW_H +#define DELFECT_QT_OFFSCREENQUICKVIEW_H + +#include +#include + +QT_FORWARD_DECLARE_CLASS(QQmlComponent) +QT_FORWARD_DECLARE_CLASS(QQmlEngine) +QT_FORWARD_DECLARE_CLASS(QQuickItem) + +namespace deflect +{ +namespace qt +{ + +/** + * The different modes of rendering. + */ +enum class RenderMode +{ + SINGLETHREADED, /**< Render and process events in the same thread */ + MULTITHREADED, /**< Render in a thread separate from event processing */ + DISABLED /**< Only process events without rendering */ +}; + +class QuickRenderer; + +/** + * An offscreen Qt Quick window, similar to a QQuickView. + */ +class OffscreenQuickView : public QQuickWindow +{ + Q_OBJECT + +public: + /** + * Create an offscreen qml view. + * @param control the render control that will be used by the view. + * @param mode the rendering mode + */ + OffscreenQuickView( std::unique_ptr control, + RenderMode mode = RenderMode::MULTITHREADED ); + + /** Close the view, stopping the rendering. */ + ~OffscreenQuickView(); + + /** + * Load a qml file to render. + * @param url qml file to load (will happen asynchronously if it is remote). + * @return a future which will indicate the success of the operation. + */ + std::future load( const QUrl& url ); + + /** @return the root qml item after a successful load, else nullptr. */ + QQuickItem* getRootItem() const; + + /** @return the internal qml engine. */ + QQmlEngine* getEngine() const; + + /** @return the root qml context. */ + QQmlContext* getRootContext() const; + +signals: + /** + * Notify that the scene has just finished rendering. + * + * @param image the newly rendered image. + * @note this signal is emitted from the render thread. It is generally + * better to connect to it using an auto (queued) connection. + */ + void afterRender( QImage image ); + +private: + std::unique_ptr _renderControl; + const RenderMode _mode; + + std::unique_ptr _qmlEngine; + std::unique_ptr _qmlComponent; + std::unique_ptr _rootItem; + + std::unique_ptr _quickRendererThread; + std::unique_ptr _quickRenderer; + + std::promise _loadPromise; + + int _renderTimer = 0; + int _stopRenderingDelayTimer = 0; + + void timerEvent( QTimerEvent* e ) final; + + void _setupRootItem(); + void _requestRender(); + void _initRenderer(); + void _render(); + void _afterRender(); +}; + +} +} + +#endif diff --git a/deflect/qt/QmlGestures.h b/deflect/qt/QmlGestures.h index 593e722..1252024 100644 --- a/deflect/qt/QmlGestures.h +++ b/deflect/qt/QmlGestures.h @@ -37,8 +37,8 @@ /* or implied, of Ecole polytechnique federale de Lausanne. */ /*********************************************************************/ -#ifndef QMLGESTURES_H -#define QMLGESTURES_H +#ifndef DELFECT_QT_QMLGESTURES_H +#define DELFECT_QT_QMLGESTURES_H #include diff --git a/deflect/qt/QmlStreamer.cpp b/deflect/qt/QmlStreamer.cpp index 484b7ca..094cbb3 100644 --- a/deflect/qt/QmlStreamer.cpp +++ b/deflect/qt/QmlStreamer.cpp @@ -56,9 +56,7 @@ QmlStreamer::QmlStreamer( const QString& qmlFile, this, &QmlStreamer::streamClosed ); } -QmlStreamer::~QmlStreamer() -{ -} +QmlStreamer::~QmlStreamer() {} void QmlStreamer::useAsyncSend( const bool async ) { diff --git a/deflect/qt/QmlStreamer.h b/deflect/qt/QmlStreamer.h index 7c891df..b24162b 100644 --- a/deflect/qt/QmlStreamer.h +++ b/deflect/qt/QmlStreamer.h @@ -37,8 +37,8 @@ /* or implied, of The University of Texas at Austin. */ /*********************************************************************/ -#ifndef QMLSTREAMER_H -#define QMLSTREAMER_H +#ifndef DELFECT_QT_QMLSTREAMER_H +#define DELFECT_QT_QMLSTREAMER_H #include diff --git a/deflect/qt/QmlStreamerImpl.cpp b/deflect/qt/QmlStreamerImpl.cpp index 228b487..bf5a150 100644 --- a/deflect/qt/QmlStreamerImpl.cpp +++ b/deflect/qt/QmlStreamerImpl.cpp @@ -41,19 +41,14 @@ #include "QmlStreamerImpl.h" #include "EventReceiver.h" -#include "QuickRenderer.h" +#include "helpers.h" #include "QmlGestures.h" #include "TouchInjector.h" #include -#include -#include -#include #include -#include #include #include -#include namespace { @@ -62,13 +57,11 @@ const QString GESTURES_CONTEXT_PROPERTY( "deflectgestures" ); const QString WEBENGINEVIEW_OBJECT_NAME( "webengineview" ); const int TOUCH_TAPANDHOLD_DIST_PX = 20; const int TOUCH_TAPANDHOLD_TIMEOUT_MS = 200; - -deflect::Stream::Future make_ready_future( const bool value ) -{ - std::promise< bool > promise; - promise.set_value( value ); - return promise.get_future(); -} +#ifdef DEFLECTQT_MULTITHREADED +const auto renderMode = deflect::qt::RenderMode::MULTITHREADED; +#else +const auto renderMode = deflect::qt::RenderMode::SINGLETHREADED; +#endif } namespace deflect @@ -78,142 +71,48 @@ namespace qt QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, const std::string& streamId ) - : _renderControl( new QQuickRenderControl ) - // Create a QQuickWindow that is associated with out render control. Note - // that this window never gets created or shown, meaning that it will never - // get an underlying native (platform) window. - , _quickWindow( new QQuickWindow( _renderControl )) - , _qmlEngine( new QQmlEngine ) - , _qmlComponent( new QQmlComponent( _qmlEngine, QUrl( qmlFile ))) - , _qmlGestures( new QmlGestures ) - , _touchInjector( new TouchInjector( *_quickWindow, - std::bind( &Impl::_mapToScene, this, - std::placeholders::_1 ))) + : _quickView{ new OffscreenQuickView{ make_unique(), + renderMode }} + , _qmlGestures{ new QmlGestures } + , _touchInjector{ TouchInjector::create( *_quickView )} , _streamHost( streamHost ) , _streamId( streamId ) , _sendFuture( make_ready_future( true )) { _setupMouseModeSwitcher(); + _setupSizeHintsConnections(); - // Expose stream gestures to qml objects - _qmlEngine->rootContext()->setContextProperty( GESTURES_CONTEXT_PROPERTY, - _qmlGestures ); - - if( !_qmlEngine->incubationController( )) - _qmlEngine->setIncubationController( _quickWindow->incubationController( )); - - // Now hook up the signals. For simplicy we don't differentiate between - // renderRequested (only render is needed, no sync) and sceneChanged (polish - // and sync is needed too). - connect( _renderControl, &QQuickRenderControl::renderRequested, - this, &QmlStreamer::Impl::_requestRender ); - connect( _renderControl, &QQuickRenderControl::sceneChanged, - this, &QmlStreamer::Impl::_requestRender ); - - // remote URL to QML components are loaded asynchronously - if( _qmlComponent->isLoading( )) - connect( _qmlComponent, &QQmlComponent::statusChanged, - this, &QmlStreamer::Impl::_setupRootItem ); - else if( !_setupRootItem( )) - throw std::runtime_error( "Failed to setup/load QML" ); -} + connect( _quickView.get(), &OffscreenQuickView::afterRender, + this, &QmlStreamer::Impl::_afterRender ); -QmlStreamer::Impl::~Impl() -{ - _quickRenderer->stop(); -#ifdef DEFLECTQT_MULTITHREADED - _quickRendererThread.quit(); - _quickRendererThread.wait(); -#endif - - // delete first to free scenegraph resources for following destructions - delete _renderControl; - - delete _rootItem; - delete _qmlComponent; - delete _quickWindow; - delete _qmlEngine; - - delete _quickRenderer; -} - -void QmlStreamer::Impl::_requestRender() -{ - killTimer( _stopRenderingDelayTimer ); - _stopRenderingDelayTimer = 0; + // Expose stream gestures to qml objects + auto context = _quickView->getRootContext(); + context->setContextProperty( GESTURES_CONTEXT_PROPERTY, _qmlGestures.get()); - if( _renderTimer == 0 ) - _renderTimer = startTimer( 5, Qt::PreciseTimer ); + if( !_quickView->load( qmlFile ).get( )) + throw std::runtime_error( "Failed to setup/load QML" ); } -void QmlStreamer::Impl::_initRenderer() -{ - _updateSizes( QSize( _rootItem->width(), _rootItem->height( ))); - -#ifdef DEFLECTQT_MULTITHREADED - _quickRenderer = new QuickRenderer( *_quickWindow, *_renderControl, true, - true ); - - // Call required to make QtGraphicalEffects work in the initial scene. - _renderControl->prepareThread( &_quickRendererThread ); - - _quickRenderer->moveToThread( &_quickRendererThread ); - - _quickRendererThread.setObjectName( "Render" ); - _quickRendererThread.start(); -#else - _quickRenderer = new QuickRenderer( *_quickWindow, *_renderControl, false, - true ); -#endif - - _quickRenderer->init(); - - connect( _quickRenderer, &QuickRenderer::afterRender, - this, &QmlStreamer::Impl::_afterRender, Qt::DirectConnection ); - connect( _quickRenderer, &QuickRenderer::afterStop, - this, &QmlStreamer::Impl::_afterStop, Qt::DirectConnection ); -} +QmlStreamer::Impl::~Impl() {} -void QmlStreamer::Impl::_render() +void QmlStreamer::Impl::_afterRender( const QImage image ) { - // Initialize the render control and our OpenGL resources. Do this as late - // as possible to use the proper size reported by the rootItem. - if( !_quickRenderer ) - _initRenderer(); - - _renderControl->polishItems(); - _quickRenderer->render(); - - if( _stopRenderingDelayTimer == 0 ) - _stopRenderingDelayTimer = startTimer( 5000 /*ms*/ ); -} + if( !_sendFuture.valid() || !_sendFuture.get( )) + return; -void QmlStreamer::Impl::_afterRender() -{ - if( _stream ) - _streaming = _sendFuture.get(); - else + if( !_stream && !_setupDeflectStream( )) { - if( !_setupDeflectStream( )) - { - qWarning() << "Could not setup Deflect stream"; - _streaming = false; - return; - } - _streaming = true; - } - - if( !_streaming ) + qWarning() << "Could not setup Deflect stream"; return; + } - _quickRenderer->context()->functions()->glFlush(); - _image = _quickRenderer->fbo()->toImage(); - if( _image.isNull( )) + if( image.isNull( )) { qDebug() << "Empty image not streamed"; return; } + _image = image; ImageWrapper imageWrapper( _image.constBits(), _image.width(), _image.height(), BGRA ); imageWrapper.compressionPolicy = COMPRESSION_ON; @@ -226,20 +125,19 @@ void QmlStreamer::Impl::_afterRender() _stream->finishFrame( )); } -void QmlStreamer::Impl::_afterStop() +void QmlStreamer::Impl::_onStreamClosed() { - if( _sendFuture.valid() && _asyncSend ) + // Stop rendering + disconnect( _quickView.get(), &OffscreenQuickView::afterRender, + this, &QmlStreamer::Impl::_afterRender ); + _quickView.reset(); + + // Terminate the stream + if( _sendFuture.valid( )) _sendFuture.wait(); - delete _eventHandler; - delete _stream; -} + _eventReceiver.reset(); + _stream.reset(); -void QmlStreamer::Impl::_onStreamClosed() -{ - killTimer( _renderTimer ); - _renderTimer = 0; - killTimer( _stopRenderingDelayTimer ); - _stopRenderingDelayTimer = 0; emit streamClosed(); } @@ -290,12 +188,12 @@ void QmlStreamer::Impl::_send( QKeyEvent& keyEvent_ ) if( _sendToWebengineviewItems( keyEvent_ )) return; - const auto items = _rootItem->findChildren(); + const auto items = _quickView->getRootItem()->findChildren(); for( auto item : items ) { if( item->hasFocus( )) { - _quickWindow->sendEvent( item, &keyEvent_ ); + _quickView->sendEvent( item, &keyEvent_ ); if( keyEvent_.isAccepted()) break; } @@ -306,8 +204,8 @@ bool QmlStreamer::Impl::_sendToWebengineviewItems( QKeyEvent& keyEvent_ ) { // Special handling for WebEngineView in offscreen Qml windows. - const auto items = - _rootItem->findChildren( WEBENGINEVIEW_OBJECT_NAME ); + const auto root = _quickView->getRootItem(); + const auto items = root->findChildren( WEBENGINEVIEW_OBJECT_NAME ); for( auto webengineviewItem : items ) { if( webengineviewItem->hasFocus( )) @@ -321,52 +219,20 @@ bool QmlStreamer::Impl::_sendToWebengineviewItems( QKeyEvent& keyEvent_ ) return false; } -bool QmlStreamer::Impl::_setupRootItem() +void QmlStreamer::Impl::_setupSizeHintsConnections() { - disconnect( _qmlComponent, &QQmlComponent::statusChanged, - this, &QmlStreamer::Impl::_setupRootItem ); - - if( _qmlComponent->isError( )) - { - QList< QQmlError > errorList = _qmlComponent->errors(); - foreach( const QQmlError &error, errorList ) - qWarning() << error.url() << error.line() << error; - return false; - } - - QObject* rootObject = _qmlComponent->create(); - if( _qmlComponent->isError( )) - { - QList< QQmlError > errorList = _qmlComponent->errors(); - foreach( const QQmlError &error, errorList ) - qWarning() << error.url() << error.line() << error; - return false; - } - - _rootItem = qobject_cast< QQuickItem* >( rootObject ); - if( !_rootItem ) - { - delete rootObject; - return false; - } - - connect( _quickWindow, &QQuickWindow::minimumWidthChanged, + connect( _quickView.get(), &QQuickWindow::minimumWidthChanged, [this]( int size_ ) { _sizeHints.minWidth = size_; } ); - connect( _quickWindow, &QQuickWindow::minimumHeightChanged, + connect( _quickView.get(), &QQuickWindow::minimumHeightChanged, [this]( int size_ ) { _sizeHints.minHeight = size_; } ); - connect( _quickWindow, &QQuickWindow::maximumWidthChanged, + connect( _quickView.get(), &QQuickWindow::maximumWidthChanged, [this]( int size_ ) { _sizeHints.maxWidth = size_; } ); - connect( _quickWindow, &QQuickWindow::maximumHeightChanged, + connect( _quickView.get(), &QQuickWindow::maximumHeightChanged, [this]( int size_ ) { _sizeHints.maxHeight = size_; } ); - connect( _quickWindow, &QQuickWindow::widthChanged, + connect( _quickView.get(), &QQuickWindow::widthChanged, [this]( int size_ ) { _sizeHints.preferredWidth = size_; } ); - connect( _quickWindow, &QQuickWindow::heightChanged, + connect( _quickView.get(), &QQuickWindow::heightChanged, [this]( int size_ ) { _sizeHints.preferredHeight = size_; } ); - - // The root item is ready. Associate it with the window and get sizeHints. - _rootItem->setParentItem( _quickWindow->contentItem( )); - - return true; } std::string QmlStreamer::Impl::_getDeflectStreamIdentifier() const @@ -374,14 +240,14 @@ std::string QmlStreamer::Impl::_getDeflectStreamIdentifier() const if( !_streamId.empty( )) return _streamId; - const std::string streamId = _rootItem->objectName().toStdString(); + const auto streamId = _quickView->getRootItem()->objectName().toStdString(); return streamId.empty() ? DEFAULT_STREAM_ID : streamId; } bool QmlStreamer::Impl::_setupDeflectStream() { if( !_stream ) - _stream = new Stream( _getDeflectStreamIdentifier(), _streamHost ); + _stream.reset( new Stream( _getDeflectStreamIdentifier(), _streamHost)); if( !_stream->isConnected( )) return false; @@ -392,67 +258,53 @@ bool QmlStreamer::Impl::_setupDeflectStream() if( _sizeHints != SizeHints( )) _stream->sendSizeHints( _sizeHints ); - _streaming = true; - _eventHandler = new EventReceiver( *_stream ); + _eventReceiver.reset( new EventReceiver( *_stream )); // inject touch events _connectTouchInjector(); // used for mouse mode switch for Qml WebengineView - connect( _eventHandler, &EventReceiver::pressed, + connect( _eventReceiver.get(), &EventReceiver::pressed, this, &QmlStreamer::Impl::_onPressed ); - connect( _eventHandler, &EventReceiver::released, + connect( _eventReceiver.get(), &EventReceiver::released, this, &QmlStreamer::Impl::_onReleased ); - connect( _eventHandler, &EventReceiver::moved, + connect( _eventReceiver.get(), &EventReceiver::moved, this, &QmlStreamer::Impl::_onMoved ); // handle view resize - connect( _eventHandler, &EventReceiver::resized, - this, &QmlStreamer::Impl::_updateSizes ); + connect( _eventReceiver.get(), &EventReceiver::resized, + [this]( const QSize size ) { _quickView->resize( size ); } ); // inject key events - connect( _eventHandler, &EventReceiver::keyPress, + connect( _eventReceiver.get(), &EventReceiver::keyPress, this, &QmlStreamer::Impl::_onKeyPress ); - connect( _eventHandler, &EventReceiver::keyRelease, + connect( _eventReceiver.get(), &EventReceiver::keyRelease, this, &QmlStreamer::Impl::_onKeyRelease ); // Forward gestures to Qml context object - connect( _eventHandler, &EventReceiver::swipeDown, - _qmlGestures, &QmlGestures::swipeDown ); - connect( _eventHandler, &EventReceiver::swipeUp, - _qmlGestures, &QmlGestures::swipeUp ); - connect( _eventHandler, &EventReceiver::swipeLeft, - _qmlGestures, &QmlGestures::swipeLeft ); - connect( _eventHandler, &EventReceiver::swipeRight, - _qmlGestures, &QmlGestures::swipeRight ); - - connect( _eventHandler, &EventReceiver::closed, + connect( _eventReceiver.get(), &EventReceiver::swipeDown, + _qmlGestures.get(), &QmlGestures::swipeDown ); + connect( _eventReceiver.get(), &EventReceiver::swipeUp, + _qmlGestures.get(), &QmlGestures::swipeUp ); + connect( _eventReceiver.get(), &EventReceiver::swipeLeft, + _qmlGestures.get(), &QmlGestures::swipeLeft ); + connect( _eventReceiver.get(), &EventReceiver::swipeRight, + _qmlGestures.get(), &QmlGestures::swipeRight ); + + connect( _eventReceiver.get(), &EventReceiver::closed, this, &QmlStreamer::Impl::_onStreamClosed ); return true; } -void QmlStreamer::Impl::_updateSizes( const QSize& size_ ) -{ - _quickWindow->blockSignals( true ); - _quickWindow->resize( size_ ); - _quickWindow->blockSignals( false ); - // emulate QQuickView::ResizeMode:SizeRootObjectToView - if( _rootItem ) - { - _rootItem->setWidth( size_.width( )); - _rootItem->setHeight( size_.height( )); - } -} - void QmlStreamer::Impl::_connectTouchInjector() { - connect( _eventHandler, &EventReceiver::touchPointAdded, - _touchInjector, &TouchInjector::addTouchPoint ); - connect( _eventHandler, &EventReceiver::touchPointUpdated, - _touchInjector, &TouchInjector::updateTouchPoint ); - connect( _eventHandler, &EventReceiver::touchPointRemoved, - _touchInjector, &TouchInjector::removeTouchPoint ); + connect( _eventReceiver.get(), &EventReceiver::touchPointAdded, + _touchInjector.get(), &TouchInjector::addTouchPoint ); + connect( _eventReceiver.get(), &EventReceiver::touchPointUpdated, + _touchInjector.get(), &TouchInjector::updateTouchPoint ); + connect( _eventReceiver.get(), &EventReceiver::touchPointRemoved, + _touchInjector.get(), &TouchInjector::removeTouchPoint ); } void QmlStreamer::Impl::_setupMouseModeSwitcher() @@ -467,7 +319,7 @@ void QmlStreamer::Impl::_setupMouseModeSwitcher() void QmlStreamer::Impl::_startMouseModeSwitchDetection( const QPointF& pos ) { - const auto item = _rootItem->childAt( pos.x(), pos.y( )); + const auto item = _quickView->getRootItem()->childAt( pos.x(), pos.y( )); if( item->objectName() == WEBENGINEVIEW_OBJECT_NAME ) { _mouseModeTimer.start(); @@ -484,7 +336,7 @@ bool QmlStreamer::Impl::_touchIsTapAndHold() void QmlStreamer::Impl::_switchFromTouchToMouseMode() { - _eventHandler->disconnect( _touchInjector ); + _eventReceiver->disconnect( _touchInjector.get( )); _touchInjector->removeAllTouchPoints(); _mouseMode = true; _sendMouseEvent( QEvent::MouseButtonPress, _touchCurrentPos ); @@ -498,8 +350,8 @@ void QmlStreamer::Impl::_switchBackToTouchMode() QPointF QmlStreamer::Impl::_mapToScene( const QPointF& normalizedPos ) const { - return { normalizedPos.x() * _quickWindow->width(), - normalizedPos.y() * _quickWindow->height() }; + return { normalizedPos.x() * _quickView->width(), + normalizedPos.y() * _quickView->height() }; } void QmlStreamer::Impl::_sendMouseEvent( const QEvent::Type eventType, @@ -507,20 +359,7 @@ void QmlStreamer::Impl::_sendMouseEvent( const QEvent::Type eventType, { auto e = new QMouseEvent( eventType, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier ); - QCoreApplication::postEvent( _quickWindow, e ); -} - -void QmlStreamer::Impl::timerEvent( QTimerEvent* e ) -{ - if( e->timerId() == _renderTimer ) - _render(); - else if( e->timerId() == _stopRenderingDelayTimer ) - { - killTimer( _renderTimer ); - killTimer( _stopRenderingDelayTimer ); - _renderTimer = 0; - _stopRenderingDelayTimer = 0; - } + QCoreApplication::postEvent( _quickView.get(), e ); } } diff --git a/deflect/qt/QmlStreamerImpl.h b/deflect/qt/QmlStreamerImpl.h index 8a97df7..de425ea 100644 --- a/deflect/qt/QmlStreamerImpl.h +++ b/deflect/qt/QmlStreamerImpl.h @@ -38,25 +38,20 @@ /* or implied, of The University of Texas at Austin. */ /*********************************************************************/ -#ifndef QMLSTREAMERIMPL_H -#define QMLSTREAMERIMPL_H +#ifndef DELFECT_QT_QMLSTREAMERIMPL_H +#define DELFECT_QT_QMLSTREAMERIMPL_H #include #include #include #include +#include "OffscreenQuickView.h" #include "QmlStreamer.h" #include "../SizeHints.h" #include -QT_FORWARD_DECLARE_CLASS(QQmlComponent) -QT_FORWARD_DECLARE_CLASS(QQmlEngine) -QT_FORWARD_DECLARE_CLASS(QQuickItem) -QT_FORWARD_DECLARE_CLASS(QQuickRenderControl) -QT_FORWARD_DECLARE_CLASS(QQuickWindow) - namespace deflect { @@ -77,24 +72,15 @@ class QmlStreamer::Impl : public QObject ~Impl(); void useAsyncSend( const bool async ) { _asyncSend = async; } - QQuickItem* getRootItem() { return _rootItem; } - QQmlEngine* getQmlEngine() { return _qmlEngine; } - Stream* getStream() { return _stream; } + QQuickItem* getRootItem() { return _quickView->getRootItem(); } + QQmlEngine* getQmlEngine() { return _quickView->getEngine(); } + Stream* getStream() { return _stream.get(); } signals: void streamClosed(); -protected: - void timerEvent( QTimerEvent* e ) final; - private slots: - bool _setupRootItem(); - - void _render(); - void _requestRender(); - - void _afterRender(); - void _afterStop(); + void _afterRender( QImage image ); void _onPressed( QPointF position ); void _onReleased( QPointF position ); @@ -106,12 +92,11 @@ private slots: void _onStreamClosed(); private: - void _initRenderer(); + void _setupSizeHintsConnections(); void _send( QKeyEvent& keyEvent ); bool _sendToWebengineviewItems( QKeyEvent& keyEvent ); std::string _getDeflectStreamIdentifier() const; bool _setupDeflectStream(); - void _updateSizes( const QSize& size ); void _connectTouchInjector(); void _setupMouseModeSwitcher(); @@ -123,34 +108,25 @@ private slots: QPointF _mapToScene( const QPointF& normalizedPos ) const; - QQuickRenderControl* _renderControl; - QuickRenderer* _quickRenderer{ nullptr }; - QThread _quickRendererThread; - QQuickWindow* _quickWindow; - QQmlEngine* _qmlEngine; - QQmlComponent* _qmlComponent; - QQuickItem* _rootItem{ nullptr }; - - int _renderTimer{ 0 }; - int _stopRenderingDelayTimer{ 0 }; - - Stream* _stream{ nullptr }; - EventReceiver* _eventHandler{ nullptr }; - QmlGestures* _qmlGestures; - TouchInjector* _touchInjector; - bool _streaming{ false }; + std::unique_ptr _quickView; + + std::unique_ptr _stream; + std::unique_ptr _eventReceiver; + std::unique_ptr _qmlGestures; + std::unique_ptr _touchInjector; + const std::string _streamHost; const std::string _streamId; SizeHints _sizeHints; + bool _asyncSend{ false }; + Stream::Future _sendFuture; + QImage _image; + QTimer _mouseModeTimer; bool _mouseMode{ false }; QPointF _touchStartPos; QPointF _touchCurrentPos; - - bool _asyncSend { false }; - Stream::Future _sendFuture; - QImage _image; }; } diff --git a/deflect/qt/QuickRenderer.cpp b/deflect/qt/QuickRenderer.cpp index b9146ac..e509fc9 100644 --- a/deflect/qt/QuickRenderer.cpp +++ b/deflect/qt/QuickRenderer.cpp @@ -59,11 +59,11 @@ namespace qt QuickRenderer::QuickRenderer( QQuickWindow& quickWindow, QQuickRenderControl& renderControl, const bool multithreaded, - const bool offscreen ) + const RenderTarget target ) : _quickWindow( quickWindow ) , _renderControl( renderControl ) , _multithreaded( multithreaded ) - , _offscreen( offscreen ) + , _renderTarget( target ) , _initialized( false ) { const auto connectionType = multithreaded ? @@ -75,6 +75,8 @@ QuickRenderer::QuickRenderer( QQuickWindow& quickWindow, &QuickRenderer::_onStop, connectionType ); } +QuickRenderer::~QuickRenderer() {} + void QuickRenderer::render() { if( _multithreaded ) @@ -101,12 +103,15 @@ bool QuickRenderer::event( QEvent* e ) void QuickRenderer::_onInit() { + if( _renderTarget == RenderTarget::NONE ) + return; + // Qt Quick may need a depth and stencil buffer QSurfaceFormat format_; format_.setDepthBufferSize( 16 ); format_.setStencilBufferSize( 8 ); - _context = new QOpenGLContext; + _context.reset( new QOpenGLContext ); _context->setFormat( format_ ); _context->create(); @@ -114,14 +119,14 @@ void QuickRenderer::_onInit() // If so, setup global share context needed by the Qml WebEngineView. if( QCoreApplication::testAttribute( Qt::AA_ShareOpenGLContexts )) #if DEFLECT_USE_QT5GUI - qt_gl_set_global_share_context( _context ); + qt_gl_set_global_share_context( _context.get( )); #else qWarning() << "DeflectQt was not compiled with WebEngineView support"; #endif - if( _offscreen ) + if( _renderTarget == RenderTarget::FBO ) { - _offscreenSurface = new QOffscreenSurface; + _offscreenSurface.reset( new QOffscreenSurface ); // Pass _context->format(), not format_. Format does not specify and color // buffer sizes, while the context, that has just been created, reports a // format that has these values filled in. Pass this to the offscreen @@ -132,57 +137,51 @@ void QuickRenderer::_onInit() } _context->makeCurrent( _getSurface( )); - _renderControl.initialize( _context ); + _renderControl.initialize( _context.get( )); _initialized = true; } void QuickRenderer::_onStop() { - _context->makeCurrent( _getSurface( )); + if( _context ) + _context->makeCurrent( _getSurface( )); _renderControl.invalidate(); - delete _fbo; - _fbo = nullptr; + _fbo.reset(); - _context->doneCurrent(); + if( _context ) + _context->doneCurrent(); - delete _offscreenSurface; - _offscreenSurface = nullptr; - delete _context; - _context = nullptr; - - emit afterStop(); + _offscreenSurface.reset(); +#if DEFLECT_USE_QT5GUI + qt_gl_set_global_share_context( nullptr ); +#endif + _context.reset(); } void QuickRenderer::_ensureFBO() { - if( _fbo && - _fbo->size() != _quickWindow.size() * _quickWindow.devicePixelRatio()) - { - delete _fbo; - _fbo = nullptr; - } + const auto winSize = _quickWindow.size() * _quickWindow.devicePixelRatio(); - if( !_fbo ) + if( !_fbo || _fbo->size() != winSize ) { - _fbo = new QOpenGLFramebufferObject( - _quickWindow.size() * _quickWindow.devicePixelRatio(), - QOpenGLFramebufferObject::CombinedDepthStencil ); - _quickWindow.setRenderTarget( _fbo ); + const auto attachment = QOpenGLFramebufferObject::CombinedDepthStencil; + _fbo.reset( new QOpenGLFramebufferObject( winSize, attachment )); + _quickWindow.setRenderTarget( _fbo.get( )); } } QSurface* QuickRenderer::_getSurface() { - if( _offscreen ) - return _offscreenSurface; + if( _offscreenSurface ) + return _offscreenSurface.get(); return &_quickWindow; } void QuickRenderer::_onRender() { - if( !_initialized ) + if( !_initialized || _renderTarget == RenderTarget::NONE ) return; { @@ -190,7 +189,7 @@ void QuickRenderer::_onRender() _context->makeCurrent( _getSurface( )); - if( _offscreen ) + if( _renderTarget == RenderTarget::FBO ) _ensureFBO(); _renderControl.sync(); diff --git a/deflect/qt/QuickRenderer.h b/deflect/qt/QuickRenderer.h index 8c9a659..a5d0655 100644 --- a/deflect/qt/QuickRenderer.h +++ b/deflect/qt/QuickRenderer.h @@ -37,9 +37,10 @@ /* or implied, of Ecole polytechnique federale de Lausanne. */ /*********************************************************************/ -#ifndef QUICKRENDERER_H -#define QUICKRENDERER_H +#ifndef DELFECT_QT_QUICKRENDERER_H +#define DELFECT_QT_QUICKRENDERER_H +#include #include #include #include @@ -56,6 +57,16 @@ namespace deflect namespace qt { +/** + * The different targets for rendering. + */ +enum class RenderTarget +{ + WINDOW, /**< Render inside the window */ + FBO, /**< Render inside an FBO (offscreen) */ + NONE /**< Only process events without rendering */ +}; + /** * Renders the QML scene from the given window using QQuickRenderControl onto * the surface of the window or to an offscreen surface. Note that this object @@ -79,22 +90,25 @@ class QuickRenderer : public QObject * trigger the actual rendering. * @param multithreaded whether the QuickRenderer is used in a multithreaded * fashion and should setup accordingly - * @param offscreen render into an offscreen surface rather than the - * quickWindow. It creates an FBO internally to hold the - * rendered pixels. + * @param target defines where the rendering should happen. An FBO is + * internally created to hold the rendered pixels if needed. */ QuickRenderer( QQuickWindow& quickWindow, QQuickRenderControl& renderControl, - bool multithreaded = true, bool offscreen = false ); + bool multithreaded = true, + RenderTarget target = RenderTarget::WINDOW ); + + /** Destructor. */ + ~QuickRenderer(); /** @return OpenGL context used for rendering; lives in render thread. */ - QOpenGLContext* context() { return _context; } + QOpenGLContext* context() { return _context.get(); } /** - * @return nullptr if !offscreen, otherwise accessible in afterRender(); + * @return nullptr if target != FBO, otherwise accessible in afterRender(); * lives in render thread. */ - QOpenGLFramebufferObject* fbo() { return _fbo; } + QOpenGLFramebufferObject* fbo() { return _fbo.get(); } /** * To be called from GUI/main thread to trigger rendering. @@ -110,9 +124,6 @@ class QuickRenderer : public QObject */ void afterRender(); - /** Emitted at the end of stop(). Originates from render thread. */ - void afterStop(); - /** * To be called from GUI/main thread to initialize this object on render * thread. Blocks until operation on render thread is done. @@ -137,12 +148,12 @@ class QuickRenderer : public QObject QQuickWindow& _quickWindow; QQuickRenderControl& _renderControl; - QOpenGLContext* _context{ nullptr }; - QOffscreenSurface* _offscreenSurface{ nullptr }; - QOpenGLFramebufferObject* _fbo{ nullptr }; + std::unique_ptr _context; + std::unique_ptr _offscreenSurface; + std::unique_ptr _fbo; - bool _multithreaded; - bool _offscreen; + const bool _multithreaded; + const RenderTarget _renderTarget; bool _initialized{ false }; QMutex _mutex; diff --git a/deflect/qt/TouchInjector.cpp b/deflect/qt/TouchInjector.cpp index 472ad05..16590c2 100644 --- a/deflect/qt/TouchInjector.cpp +++ b/deflect/qt/TouchInjector.cpp @@ -39,7 +39,10 @@ #include "TouchInjector.h" +#include "helpers.h" + #include +#include namespace deflect { @@ -53,6 +56,16 @@ TouchInjector::TouchInjector( QObject& target, MapToSceneFunc mapFunc ) _device.setType( QTouchDevice::TouchScreen ); } +std::unique_ptr TouchInjector::create( QWindow& window ) +{ + auto mapFunc = [&window]( const QPointF& normPos ) + { + return QPointF{ normPos.x() * window.width(), + normPos.y() * window.height() }; + }; + return make_unique( window, mapFunc ); +} + void TouchInjector::addTouchPoint( const int id, const QPointF position ) { if( _touchPointMap.contains( id )) diff --git a/deflect/qt/TouchInjector.h b/deflect/qt/TouchInjector.h index 9ffad0f..6f68c04 100644 --- a/deflect/qt/TouchInjector.h +++ b/deflect/qt/TouchInjector.h @@ -37,13 +37,16 @@ /* or implied, of The University of Texas at Austin. */ /*********************************************************************/ -#ifndef TOUCHINJECTOR_H -#define TOUCHINJECTOR_H +#ifndef DELFECT_QT_TOUCHINJECTOR_H +#define DELFECT_QT_TOUCHINJECTOR_H #include #include #include +#include + +class QWindow; namespace deflect { @@ -69,6 +72,12 @@ class TouchInjector : public QObject */ TouchInjector( QObject& target, MapToSceneFunc mapFunc ); + /** + * Create a touch injector for a QWindow. + * @param window the target window that should receive the touch events + */ + static std::unique_ptr create( QWindow& window ); + /** * Insert a new touch point. * diff --git a/deflect/qt/helpers.h b/deflect/qt/helpers.h new file mode 100644 index 0000000..6fbb80b --- /dev/null +++ b/deflect/qt/helpers.h @@ -0,0 +1,70 @@ +/*********************************************************************/ +/* Copyright (c) 2016, EPFL/Blue Brain Project */ +/* Raphael Dumusc */ +/* All rights reserved. */ +/* */ +/* Redistribution and use in source and binary forms, with or */ +/* without modification, are permitted provided that the following */ +/* conditions are met: */ +/* */ +/* 1. Redistributions of source code must retain the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer. */ +/* */ +/* 2. Redistributions in binary form must reproduce the above */ +/* copyright notice, this list of conditions and the following */ +/* disclaimer in the documentation and/or other materials */ +/* provided with the distribution. */ +/* */ +/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ +/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ +/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ +/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ +/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ +/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ +/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ +/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ +/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ +/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ +/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ +/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ +/* POSSIBILITY OF SUCH DAMAGE. */ +/* */ +/* The views and conclusions contained in the software and */ +/* documentation are those of the authors and should not be */ +/* interpreted as representing official policies, either expressed */ +/* or implied, of Ecole polytechnique federale de Lausanne. */ +/*********************************************************************/ + +#ifndef DELFECT_QT_HELPERS_H +#define DELFECT_QT_HELPERS_H + +#include +#include + +namespace deflect +{ +namespace qt +{ + +template +std::future make_ready_future( const T value ) +{ + std::promise promise; + promise.set_value( value ); + return promise.get_future(); +} + +// missing make_unique() implementation in C++11 standard +// source: http://herbsutter.com/gotw/_102/ +template +std::unique_ptr make_unique( Args&& ...args ) +{ + return std::unique_ptr( new T( std::forward(args)... ) ); +} + +} +} + +#endif diff --git a/doc/Changelog.md b/doc/Changelog.md index 0091e45..2457912 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -5,6 +5,9 @@ Changelog {#Changelog} ### 0.12.0 (git master) +* [137](https://github.com/BlueBrain/Deflect/pull/137): + Deflect Qt: the offscreen Qml view used by the Qml streamer is now available + as a separate class. * [133](https://github.com/BlueBrain/Deflect/pull/133): QmlStreamer: Use asynchronous rendering, add deflect::qt::QmlStreamer::useAsyncSend() to enable asynchronous image streaming