diff --git a/apps/DesktopStreamer/MainWindow.cpp b/apps/DesktopStreamer/MainWindow.cpp index d40c5d0..9da1cc4 100644 --- a/apps/DesktopStreamer/MainWindow.cpp +++ b/apps/DesktopStreamer/MainWindow.cpp @@ -80,7 +80,7 @@ MainWindow::MainWindow() { setupUi( this ); - connect( _hostnameComboBox, &QComboBox::currentTextChanged, + connect( _hostComboBox, &QComboBox::currentTextChanged, [&]( const QString& text ) { _streamButton->setEnabled( !text.isEmpty( )); @@ -88,14 +88,14 @@ MainWindow::MainWindow() }); for( const auto& entry : defaultHosts ) - _hostnameComboBox->addItem( entry.first, entry.second ); + _hostComboBox->addItem( entry.first, entry.second ); // no default host selected initially - _hostnameComboBox->setCurrentIndex( -1 ); + _hostComboBox->setCurrentIndex( -1 ); char hostname[256] = { 0 }; gethostname( hostname, 256 ); - _streamnameLineEdit->setText( QString( "%1" ).arg( hostname )); + _streamIdLineEdit->setText( QString( "%1" ).arg( hostname )); #ifdef DEFLECT_USE_QT5MACEXTRAS _listView->setModel( new DesktopWindowsModel ); @@ -195,13 +195,13 @@ void MainWindow::_updateStreams() continue; } - const std::string name = index.isValid() ? + const std::string appName = index.isValid() ? _listView->model()->data( index, Qt::DisplayRole ). toString().toStdString() : std::string(); - const std::string streamName = std::to_string( ++_streamID ) + - " " + name + " - " + - _streamnameLineEdit->text().toStdString(); - StreamPtr stream( new Stream( *this, index, streamName, host )); + const std::string streamId = std::to_string( ++_streamID ) + + " " + appName + " - " + + _streamIdLineEdit->text().toStdString(); + StreamPtr stream( new Stream( *this, index, streamId, host )); if( !stream->isConnected( )) { @@ -235,7 +235,7 @@ void MainWindow::_updateStreams() { const QPersistentModelIndex index; // default == use desktop StreamPtr stream( new Stream( *this, index, - _streamnameLineEdit->text().toStdString(), + _streamIdLineEdit->text().toStdString(), host )); if( stream->isConnected( )) { @@ -332,10 +332,10 @@ void MainWindow::_regulateFrameRate() std::string MainWindow::_getStreamHost() const { QString streamHost; - if( _hostnameComboBox->findText(_hostnameComboBox->currentText( )) == -1 ) - streamHost = _hostnameComboBox->currentText(); + if( _hostComboBox->findText(_hostComboBox->currentText( )) == -1 ) + streamHost = _hostComboBox->currentText(); else - streamHost = _hostnameComboBox->currentData().toString(); + streamHost = _hostComboBox->currentData().toString(); return streamHost.toStdString(); } diff --git a/apps/DesktopStreamer/MainWindow.ui b/apps/DesktopStreamer/MainWindow.ui index ba4340a..e074c40 100644 --- a/apps/DesktopStreamer/MainWindow.ui +++ b/apps/DesktopStreamer/MainWindow.ui @@ -58,7 +58,7 @@ - + 0 @@ -173,7 +173,7 @@ - + 0 @@ -261,7 +261,7 @@ _streamButton toggled(bool) - _streamnameLineEdit + _streamIdLineEdit setDisabled(bool) @@ -277,7 +277,7 @@ _streamButton toggled(bool) - _hostnameComboBox + _hostComboBox setDisabled(bool) diff --git a/apps/DesktopStreamer/Stream.cpp b/apps/DesktopStreamer/Stream.cpp index 532d346..173b663 100644 --- a/apps/DesktopStreamer/Stream.cpp +++ b/apps/DesktopStreamer/Stream.cpp @@ -57,8 +57,8 @@ #define CURSOR_IMAGE_SIZE 20 Stream::Stream( const MainWindow& parent, const QPersistentModelIndex window, - const std::string& name, const std::string& host ) - : deflect::Stream( name, host ) + const std::string& id, const std::string& host ) + : deflect::Stream( id, host ) , _parent( parent ) , _window( window ) , _cursor( QImage( CURSOR_IMAGE_FILE ).scaled( diff --git a/apps/DesktopStreamer/Stream.h b/apps/DesktopStreamer/Stream.h index 5fb3543..8ecc094 100644 --- a/apps/DesktopStreamer/Stream.h +++ b/apps/DesktopStreamer/Stream.h @@ -52,7 +52,7 @@ class Stream : public deflect::Stream public: /** Construct a new stream for the given desktop window. */ Stream( const MainWindow& parent, const QPersistentModelIndex window, - const std::string& name, const std::string& host ); + const std::string& id, const std::string& host ); ~Stream(); /** diff --git a/apps/QmlStreamer/main.cpp b/apps/QmlStreamer/main.cpp index b74baa4..7f09976 100644 --- a/apps/QmlStreamer/main.cpp +++ b/apps/QmlStreamer/main.cpp @@ -38,9 +38,9 @@ int main( int argc, char** argv ) "qrc:/qml/gui.qml" ); parser.addOption( qmlFileOption ); - QCommandLineOption streamHostOption( "host", "Stream target hostname " + QCommandLineOption streamHostOption( "host", "Stream target host " "(default: localhost)", - "hostname", "localhost" ); + "host", "localhost" ); parser.addOption( streamHostOption ); // note: the 'name' command line option is already taken by QCoreApplication diff --git a/apps/SimpleStreamer/main.cpp b/apps/SimpleStreamer/main.cpp index 9505047..323d579 100644 --- a/apps/SimpleStreamer/main.cpp +++ b/apps/SimpleStreamer/main.cpp @@ -57,8 +57,8 @@ bool deflectInteraction = false; bool deflectCompressImage = true; unsigned int deflectCompressionQuality = 75; -char* deflectHostname = NULL; -std::string deflectStreamName = "SimpleStreamer"; +char* deflectHost = NULL; +std::string deflectStreamId = "SimpleStreamer"; deflect::Stream* deflectStream = NULL; void syntax( char* app ); @@ -78,7 +78,7 @@ int main( int argc, char** argv ) { readCommandLineArguments( argc, argv ); - if( deflectHostname == NULL ) + if( deflectHost == NULL ) syntax( argv[0] ); initGLWindow( argc, argv ); @@ -101,7 +101,7 @@ void readCommandLineArguments( int argc, char** argv ) case 'n': if( i + 1 < argc ) { - deflectStreamName = argv[i+1]; + deflectStreamId = argv[i+1]; ++i; } break; @@ -116,7 +116,7 @@ void readCommandLineArguments( int argc, char** argv ) } } else if( i == argc - 1 ) - deflectHostname = argv[i]; + deflectHost = argv[i]; } } @@ -147,7 +147,7 @@ void initGLWindow( int argc, char** argv ) void initDeflectStream() { - deflectStream = new deflect::Stream( deflectStreamName, deflectHostname ); + deflectStream = new deflect::Stream( deflectStreamId, deflectHost ); if( !deflectStream->isConnected( )) { std::cerr << "Could not connect to host!" << std::endl; @@ -166,11 +166,11 @@ void initDeflectStream() void syntax( char* app ) { - std::cerr << "syntax: " << app << " [options] " << std::endl; + std::cerr << "syntax: " << app << " [options] " << std::endl; std::cerr << "options:" << std::endl; - std::cerr << " -n set stream name (default SimpleStreamer)" << std::endl; - std::cerr << " -i enable interaction events (default disabled)" << std::endl; - std::cerr << " -u enable uncompressed streaming (default disabled)" << std::endl; + std::cerr << " -n set stream identifier (default: 'SimpleStreamer')" << std::endl; + std::cerr << " -i enable interaction events (default: OFF)" << std::endl; + std::cerr << " -u enable uncompressed streaming (default: OFF)" << std::endl; exit( 1 ); } diff --git a/deflect/ServerWorker.cpp b/deflect/ServerWorker.cpp index 3b0f513..5041f06 100644 --- a/deflect/ServerWorker.cpp +++ b/deflect/ServerWorker.cpp @@ -81,8 +81,8 @@ ServerWorker::~ServerWorker() // We still want to remove this source so that the stream does not get stuck // if other senders are still active / resp. the window gets closed if no // more senders contribute to it. - if( !_streamUri.isEmpty( )) - emit removeStreamSource( _streamUri, _sourceId ); + if( !_streamId.isEmpty( )) + emit removeStreamSource( _streamId, _sourceId ); if( _tcpSocket->state() == QAbstractSocket::ConnectedState ) _sendQuit(); @@ -103,7 +103,7 @@ void ServerWorker::initConnection() void ServerWorker::closeConnection( const QString uri ) { - if( uri != _streamUri ) + if( uri != _streamId ) return; Event closeEvent; @@ -116,7 +116,7 @@ void ServerWorker::closeConnection( const QString uri ) void ServerWorker::replyToEventRegistration( const QString uri, const bool success ) { - if( uri != _streamUri ) + if( uri != _streamId ) return; _registeredToEvents = success; @@ -196,39 +196,38 @@ void ServerWorker::_handleMessage( const MessageHeader& messageHeader, const QString uri( messageHeader.uri ); if( uri.isEmpty( )) { - std::cerr << "Warning: rejecting streamer with empty uri" - << std::endl; - closeConnection( _streamUri ); + std::cerr << "Warning: rejecting streamer with empty id" << std::endl; + closeConnection( _streamId ); return; } - if( uri != _streamUri && + if( uri != _streamId && messageHeader.type != MESSAGE_TYPE_PIXELSTREAM_OPEN ) { - std::cerr << "Warning: ingnoring message with incorrect stream uri: '" + std::cerr << "Warning: ingnoring message with incorrect stream id: '" << messageHeader.uri << "', expected: '" - << _streamUri.toStdString() << "'" << std::endl; + << _streamId.toStdString() << "'" << std::endl; return; } switch( messageHeader.type ) { case MESSAGE_TYPE_QUIT: - emit removeStreamSource( _streamUri, _sourceId ); - _streamUri = QString(); + emit removeStreamSource( _streamId, _sourceId ); + _streamId = QString(); break; case MESSAGE_TYPE_PIXELSTREAM_OPEN: - if( !_streamUri.isEmpty( )) + if( !_streamId.isEmpty( )) { std::cerr << "Warning: PixelStream already opened!" << std::endl; return; } - _streamUri = uri; - emit addStreamSource( _streamUri, _sourceId ); + _streamId = uri; + emit addStreamSource( _streamId, _sourceId ); break; case MESSAGE_TYPE_PIXELSTREAM_FINISH_FRAME: - emit receivedFrameFinished( _streamUri, _sourceId ); + emit receivedFrameFinished( _streamId, _sourceId ); break; case MESSAGE_TYPE_PIXELSTREAM: @@ -239,7 +238,7 @@ void ServerWorker::_handleMessage( const MessageHeader& messageHeader, { const SizeHints* hints = reinterpret_cast< const SizeHints* >( byteArray.data( )); - emit receivedSizeHints( _streamUri, SizeHints( *hints )); + emit receivedSizeHints( _streamId, SizeHints( *hints )); break; } @@ -251,7 +250,7 @@ void ServerWorker::_handleMessage( const MessageHeader& messageHeader, { const bool exclusive = (messageHeader.type == MESSAGE_TYPE_BIND_EVENTS_EX); - emit registerToEvents( _streamUri, exclusive, this ); + emit registerToEvents( _streamId, exclusive, this ); } break; @@ -272,7 +271,7 @@ void ServerWorker::_handlePixelStreamMessage( const QByteArray& byteArray ) byteArray.right( byteArray.size() - sizeof( SegmentParameters )); segment.imageData = imageData; - emit( receivedSegment( _streamUri, _sourceId, segment )); + emit( receivedSegment( _streamId, _sourceId, segment )); } void ServerWorker::_sendProtocolVersion() diff --git a/deflect/ServerWorker.h b/deflect/ServerWorker.h index 1a2db81..75a04a9 100644 --- a/deflect/ServerWorker.h +++ b/deflect/ServerWorker.h @@ -92,7 +92,7 @@ private slots: private: QTcpSocket* _tcpSocket; - QString _streamUri; + QString _streamId; int _sourceId; bool _registeredToEvents; diff --git a/deflect/Socket.cpp b/deflect/Socket.cpp index d967dc4..2ea6d75 100644 --- a/deflect/Socket.cpp +++ b/deflect/Socket.cpp @@ -56,8 +56,9 @@ namespace deflect const unsigned short Socket::defaultPortNumber = DEFAULT_PORT_NUMBER; -Socket::Socket( const std::string& hostname, const unsigned short port ) - : _socket( new QTcpSocket( )) +Socket::Socket( const std::string& host, const unsigned short port ) + : _host( host ) + , _socket( new QTcpSocket( )) , _remoteProtocolVersion( INVALID_NETWORK_PROTOCOL_VERSION ) { // disable warnings which occur if no QCoreApplication is present during @@ -69,7 +70,7 @@ Socket::Socket( const std::string& hostname, const unsigned short port ) log->setEnabled( QtWarningMsg, false ); } - _connect( hostname, port ); + _connect( host, port ); QObject::connect( _socket, &QTcpSocket::disconnected, this, &Socket::disconnected ); @@ -80,6 +81,11 @@ Socket::~Socket() delete _socket; } +const std::string& Socket::getHost() const +{ + return _host; +} + bool Socket::isConnected() const { return _socket->state() == QTcpSocket::ConnectedState; @@ -185,17 +191,17 @@ bool Socket::_receiveHeader( MessageHeader& messageHeader ) return stream.status() == QDataStream::Ok; } -bool Socket::_connect( const std::string& hostname, const unsigned short port ) +bool Socket::_connect( const std::string& host, const unsigned short port ) { // make sure we're disconnected _socket->disconnectFromHost(); // open connection - _socket->connectToHost( hostname.c_str(), port ); + _socket->connectToHost( host.c_str(), port ); if( !_socket->waitForConnected( RECEIVE_TIMEOUT_MS )) { - std::cerr << "could not connect to host " << hostname << ":" << port + std::cerr << "could not connect to host " << host << ":" << port << std::endl; return false; } @@ -204,7 +210,7 @@ bool Socket::_connect( const std::string& hostname, const unsigned short port ) if( _checkProtocolVersion( )) return true; - std::cerr << "Protocol version check failed for host: " << hostname << ":" + std::cerr << "Protocol version check failed for host: " << host << ":" << port << std::endl; _socket->disconnectFromHost(); return false; diff --git a/deflect/Socket.h b/deflect/Socket.h index 448b53b..228f7b9 100644 --- a/deflect/Socket.h +++ b/deflect/Socket.h @@ -70,15 +70,18 @@ class Socket : public QObject /** * Construct a Socket and connect to host. - * @param hostname The target host (IP address or hostname) + * @param host The target host (IP address or hostname) * @param port The target port */ - DEFLECT_API Socket( const std::string& hostname, + DEFLECT_API Socket( const std::string& host, unsigned short port = defaultPortNumber ); /** Destruct a Socket, disconnecting from host. */ DEFLECT_API ~Socket(); + /** Get the host passed to the constructor. */ + const std::string& getHost() const; + /** Is the Socket connected */ DEFLECT_API bool isConnected() const; @@ -118,11 +121,12 @@ class Socket : public QObject void disconnected(); private: + const std::string _host; QTcpSocket* _socket; int32_t _remoteProtocolVersion; mutable QMutex _socketMutex; - bool _connect( const std::string &hostname, const unsigned short port ); + bool _connect( const std::string &host, const unsigned short port ); bool _checkProtocolVersion(); bool _receiveHeader( MessageHeader& messageHeader ); diff --git a/deflect/Stream.cpp b/deflect/Stream.cpp index 03b0404..9496c6d 100644 --- a/deflect/Stream.cpp +++ b/deflect/Stream.cpp @@ -54,9 +54,20 @@ namespace deflect { -Stream::Stream( const std::string& name, const std::string& address, +Stream::Stream() + : _impl( new StreamPrivate( "", "", Socket::defaultPortNumber )) +{ + if( isConnected( )) + { + _impl->socket.connect( &_impl->socket, &Socket::disconnected, + [this]() { disconnected(); }); + _impl->sendOpen(); + } +} + +Stream::Stream( const std::string& id, const std::string& host, const unsigned short port ) - : _impl( new StreamPrivate( name, address, port )) + : _impl( new StreamPrivate( id, host, port )) { if( isConnected( )) { @@ -75,6 +86,16 @@ bool Stream::isConnected() const return _impl->socket.isConnected(); } +const std::string& Stream::getId() const +{ + return _impl->id; +} + +const std::string& Stream::getHost() const +{ + return _impl->socket.getHost(); +} + bool Stream::send( const ImageWrapper& image ) { return _impl->send( image ); @@ -104,7 +125,7 @@ bool Stream::registerForEvents( const bool exclusive ) const MessageType type = exclusive ? MESSAGE_TYPE_BIND_EVENTS_EX : MESSAGE_TYPE_BIND_EVENTS; - MessageHeader mh( type, 0, _impl->name ); + MessageHeader mh( type, 0, _impl->id ); // Send the bind message if( !_impl->socket.send( mh, QByteArray( ))) diff --git a/deflect/Stream.h b/deflect/Stream.h index 85574d3..a04c5c8 100644 --- a/deflect/Stream.h +++ b/deflect/Stream.h @@ -79,6 +79,17 @@ class StreamPrivate; class Stream { public: + /** + * Open a new connection to the Server using environment variables. + * + * DEFLECT_HOST The address of the target Server instance (required). + * DEFLECT_ID The identifier for the stream. If not provided, a random + * unique identifier will be used. + * @throw std::runtime_error if DEFLECT_HOST was not provided. + * @version 1.3 + */ + DEFLECT_API Stream(); + /** * Open a new connection to the Server. * @@ -86,18 +97,22 @@ class Stream * isConnected(). * * Different Streams can contribute to a single window by using the same - * name as identifier. All the Streams which contribute to the same window - * should be created before any of them starts sending images. + * identifier. All the Streams which contribute to the same window should be + * created before any of them starts sending images. * - * @param name An identifier for the stream which cannot be empty. - * @param address Address of the target Server instance, can be a - * hostname like "localhost" or an IP in string format like - * "192.168.1.83". + * @param id The identifier for the stream. If left empty, the environment + * variable DEFLECT_ID will be used. If both values are empty, + * a random unique identifier will be used. + * @param host The address of the target Server instance. It can be a + * hostname like "localhost" or an IP in string format like + * "192.168.1.83". If left empty, the environment variable + * DEFLECT_HOST will be used instead. * @param port Port of the Server instance, default 1701. + * @throw std::runtime_error if no host was provided. * @version 1.0 */ - DEFLECT_API Stream( const std::string& name, const std::string& address, - const unsigned short port = 1701 ); + DEFLECT_API Stream( const std::string& id, const std::string& host, + unsigned short port = 1701 ); /** Destruct the Stream, closing the connection. @version 1.0 */ DEFLECT_API virtual ~Stream(); @@ -105,6 +120,12 @@ class Stream /** @return true if the stream is connected, false otherwise. @version 1.0*/ DEFLECT_API bool isConnected() const; + /** @return the identifier defined by the constructor. @version 1.3 */ + DEFLECT_API const std::string& getId() const; + + /** @return the host defined by the constructor. @version 1.3 */ + DEFLECT_API const std::string& getHost() const; + /** Emitted after the stream was disconnected. @version 1.0 */ boost::signals2::signal< void() > disconnected; @@ -147,8 +168,8 @@ class Stream * * This method must be called everytime this Stream instance has finished * sending its image(s) for the current frame. The receiver will display - * the images once all the senders which use the same name have finished a - * frame. + * the images once all the senders which use the same identifier have + * finished a frame. * * @note A call to finishFrame() while an asyncSend() is pending is * undefined. @@ -172,7 +193,7 @@ class Stream * This method is synchronous and waits for a registration reply from the * Server before returning. * - * @param exclusive Binds only one stream source for the same name + * @param exclusive Binds only one stream source for the same identifier. * @return true if the registration could be or was already established. * @version 1.0 */ diff --git a/deflect/StreamPrivate.cpp b/deflect/StreamPrivate.cpp index 45fcb3b..58c5bbc 100644 --- a/deflect/StreamPrivate.cpp +++ b/deflect/StreamPrivate.cpp @@ -50,22 +50,52 @@ #include #include -#define SEGMENT_SIZE 512 + +#include + +namespace +{ +const unsigned int SEGMENT_SIZE = 512; +const char* STREAM_ID_ENV_VAR = "DEFLECT_ID"; +const char* STREAM_HOST_ENV_VAR = "DEFLECT_HOST"; +} + +std::string _getStreamHost( const std::string& host ) +{ + if( !host.empty( )) + return host; + + const QString streamHost = qgetenv( STREAM_HOST_ENV_VAR ).constData(); + if( !streamHost.isEmpty( )) + return streamHost.toStdString(); + + throw std::runtime_error( "No host provided" ); +} + +std::string _getStreamId( const std::string& id ) +{ + if( !id.empty( )) + return id; + + const QString streamId = qgetenv( STREAM_ID_ENV_VAR ).constData(); + if( !streamId.isEmpty( )) + return streamId.toStdString(); + + return QString( "%1_%2" ).arg( QHostInfo::localHostName(), + QString::number( rand(), 16 )).toStdString(); +} namespace deflect { -StreamPrivate::StreamPrivate( const std::string &name_, - const std::string& address, +StreamPrivate::StreamPrivate( const std::string& id_, + const std::string& host, const unsigned short port ) - : name( name_ ) - , socket( address, port ) + : id( _getStreamId( id_ )) + , socket( _getStreamHost( host ), port ) , registeredForEvents( false ) { imageSegmenter.setNominalSegmentDimensions( SEGMENT_SIZE, SEGMENT_SIZE ); - - if( name.empty( )) - throw std::runtime_error( "Invalid Stream name: " + name ); } StreamPrivate::~StreamPrivate() @@ -80,13 +110,13 @@ StreamPrivate::~StreamPrivate() void StreamPrivate::sendOpen() { - const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM_OPEN, 0, name ); + const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM_OPEN, 0, id ); socket.send( mh, QByteArray( )); } void StreamPrivate::sendClose() { - const MessageHeader mh( MESSAGE_TYPE_QUIT, 0, name ); + const MessageHeader mh( MESSAGE_TYPE_QUIT, 0, id ); socket.send( mh, QByteArray( )); } @@ -116,7 +146,7 @@ Stream::Future StreamPrivate::asyncSend( const ImageWrapper& image ) bool StreamPrivate::finishFrame() { // Open a window for the PixelStream - const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM_FINISH_FRAME, 0, name ); + const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM_FINISH_FRAME, 0, id ); return socket.send( mh, QByteArray( )); } @@ -125,7 +155,7 @@ bool StreamPrivate::sendPixelStreamSegment( const Segment& segment ) // Create message header const uint32_t segmentSize( sizeof( SegmentParameters ) + segment.imageData.size( )); - const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM, segmentSize, name ); + const MessageHeader mh( MESSAGE_TYPE_PIXELSTREAM, segmentSize, id ); // This byte array will hold the message to be sent over the socket QByteArray message; @@ -142,7 +172,7 @@ bool StreamPrivate::sendPixelStreamSegment( const Segment& segment ) bool StreamPrivate::sendSizeHints( const SizeHints& hints ) { - const MessageHeader mh( MESSAGE_TYPE_SIZE_HINTS, sizeof( hints ), name ); + const MessageHeader mh( MESSAGE_TYPE_SIZE_HINTS, sizeof( hints ), id ); QByteArray message; message.append( (const char*)( &hints ), sizeof( hints ) ); diff --git a/deflect/StreamPrivate.h b/deflect/StreamPrivate.h index 927f680..da776cc 100644 --- a/deflect/StreamPrivate.h +++ b/deflect/StreamPrivate.h @@ -67,16 +67,12 @@ class StreamPrivate /** * Create a new stream and open a new connection to the deflect::Server. * - * It can be a hostname like "localhost" or an IP in string format, - * e.g. "192.168.1.83" This method must be called by all Streams sharing a - * common identifier before any of them starts sending images. - * - * @param name the unique stream name - * @param address Address of the target Server instance. + * @param id the unique stream identifier + * @param host Address of the target Server instance. * @param port Port of the target Server instance. */ - StreamPrivate( const std::string& name, const std::string& address, - const unsigned short port ); + StreamPrivate( const std::string& id, const std::string& host, + unsigned short port ); /** Destructor, close the Stream. */ ~StreamPrivate(); @@ -103,10 +99,8 @@ class StreamPrivate bool finishFrame(); /** - * Send an existing PixelStreamSegment via the Socket. - * @param socket The Socket instance - * @param segment A pixel stream segement with valid parameters and imageData - * @param senderName Used to identifiy the sender on the receiver side + * Send a Segment through the Stream. + * @param segment An image segment with valid parameters and data * @return true if the message could be sent */ DEFLECT_API bool sendPixelStreamSegment( const Segment& segment ); @@ -115,7 +109,7 @@ class StreamPrivate bool sendSizeHints( const SizeHints& hints ); /** The stream identifier. */ - const std::string name; + const std::string id; /** The communication socket instance */ Socket socket; diff --git a/deflect/qt/QmlStreamer.cpp b/deflect/qt/QmlStreamer.cpp index fed18ed..4bc58ea 100644 --- a/deflect/qt/QmlStreamer.cpp +++ b/deflect/qt/QmlStreamer.cpp @@ -47,8 +47,8 @@ namespace qt QmlStreamer::QmlStreamer( const QString& qmlFile, const std::string& streamHost, - const std::string& streamName ) - : _impl( new Impl( qmlFile, streamHost, streamName )) + const std::string& streamId ) + : _impl( new Impl( qmlFile, streamHost, streamId )) { connect( _impl.get(), &Impl::streamClosed, this, &QmlStreamer::streamClosed ); diff --git a/deflect/qt/QmlStreamer.h b/deflect/qt/QmlStreamer.h index 24f03ee..0fb32fc 100644 --- a/deflect/qt/QmlStreamer.h +++ b/deflect/qt/QmlStreamer.h @@ -69,15 +69,15 @@ class QmlStreamer : public QObject * Construct a new qml streamer by loading the QML, accessible by * getRootItem() and sets up the Deflect stream. * - * @param qmlFile URL to QML file to load - * @param streamHost hostname of the Deflect server - * @param streamName name of the Deflect stream (optional). Setting this - * value overrides the 'objectName' property of the root QML item. - * If neither is provided, "QmlStreamer" is used instead. + * @param qmlFile URL to QML file to load. + * @param streamHost host where the Deflect server is running. + * @param streamId identifier for the Deflect stream (optional). Setting + * this value overrides the 'objectName' property of the root QML + * item. If neither is provided, "QmlStreamer" is used instead. */ DEFLECTQT_API QmlStreamer( const QString& qmlFile, const std::string& streamHost, - const std::string& streamName = std::string( )); + const std::string& streamId = std::string( )); DEFLECTQT_API ~QmlStreamer(); diff --git a/deflect/qt/QmlStreamerImpl.cpp b/deflect/qt/QmlStreamerImpl.cpp index 980dd24..0bd5ae3 100644 --- a/deflect/qt/QmlStreamerImpl.cpp +++ b/deflect/qt/QmlStreamerImpl.cpp @@ -53,7 +53,7 @@ namespace { -const std::string DEFAULT_STREAM_NAME( "QmlStreamer" ); +const std::string DEFAULT_STREAM_ID( "QmlStreamer" ); } class RenderControl : public QQuickRenderControl @@ -80,7 +80,7 @@ namespace qt { QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, - const std::string& streamName ) + const std::string& streamId ) : QWindow() , _context( new QOpenGLContext ) , _offscreenSurface( new QOffscreenSurface ) @@ -97,7 +97,7 @@ QmlStreamer::Impl::Impl( const QString& qmlFile, const std::string& streamHost, , _eventHandler( nullptr ) , _streaming( false ) , _streamHost( streamHost ) - , _streamName( streamName ) + , _streamId( streamId ) { setSurfaceType( QSurface::OpenGLSurface ); @@ -328,19 +328,19 @@ bool QmlStreamer::Impl::_setupRootItem() return true; } -std::string QmlStreamer::Impl::_getDeflectStreamName() const +std::string QmlStreamer::Impl::_getDeflectStreamIdentifier() const { - if( !_streamName.empty( )) - return _streamName; + if( !_streamId.empty( )) + return _streamId; - const std::string streamName = _rootItem->objectName().toStdString(); - return streamName.empty() ? DEFAULT_STREAM_NAME : streamName; + const std::string streamId = _rootItem->objectName().toStdString(); + return streamId.empty() ? DEFAULT_STREAM_ID : streamId; } bool QmlStreamer::Impl::_setupDeflectStream() { if( !_stream ) - _stream = new Stream( _getDeflectStreamName(), _streamHost ); + _stream = new Stream( _getDeflectStreamIdentifier(), _streamHost ); if( !_stream->isConnected( )) return false; diff --git a/deflect/qt/QmlStreamerImpl.h b/deflect/qt/QmlStreamerImpl.h index 8cd737f..fdd514f 100644 --- a/deflect/qt/QmlStreamerImpl.h +++ b/deflect/qt/QmlStreamerImpl.h @@ -71,7 +71,7 @@ class QmlStreamer::Impl : public QWindow public: Impl( const QString& qmlFile, const std::string& streamHost, - const std::string& streamName ); + const std::string& streamId ); ~Impl(); @@ -101,7 +101,7 @@ private slots: void streamClosed(); private: - std::string _getDeflectStreamName() const; + std::string _getDeflectStreamIdentifier() const; bool _setupDeflectStream(); void _updateSizes( const QSize& size ); @@ -119,7 +119,7 @@ private slots: EventReceiver* _eventHandler; bool _streaming; const std::string _streamHost; - const std::string _streamName; + const std::string _streamId; SizeHints _sizeHints; }; diff --git a/doc/Changelog.md b/doc/Changelog.md index 3354d58..55a105f 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -8,6 +8,8 @@ Changelog {#Changelog} QmlStreamer: correcly quit application when stream is closed. * [99](https://github.com/BlueBrain/Deflect/pull/99): Fix incomplete socket send under certain timing conditions +* [98](https://github.com/BlueBrain/Deflect/pull/98): + Streams can be constructed based on the DEFLECT_ID and DEFLECT_HOST ENV_VARs. * [95](https://github.com/BlueBrain/Deflect/pull/95): DesktopStreamer: the list of windows available for streaming is also updated after a window has been hidden or unhidden. diff --git a/tests/cpp/ServerTests.cpp b/tests/cpp/ServerTests.cpp index 6cadabb..6603263 100644 --- a/tests/cpp/ServerTests.cpp +++ b/tests/cpp/ServerTests.cpp @@ -43,18 +43,23 @@ namespace ut = boost::unit_test; #include "MinimalGlobalQtApp.h" -#include +#include #include +#include #include #include #include +namespace +{ +const QString testStreamId( "teststream" ); +} + BOOST_GLOBAL_FIXTURE( MinimalGlobalQtApp ); BOOST_AUTO_TEST_CASE( testSizeHintsReceivedByServer ) { - const QString testURI( "teststream" ); deflect::SizeHints testHints; testHints.maxWidth = 500; testHints.preferredHeight= 200; @@ -63,12 +68,15 @@ BOOST_AUTO_TEST_CASE( testSizeHintsReceivedByServer ) QWaitCondition received; QMutex mutex; + QString streamId; + deflect::SizeHints sizeHints; + deflect::Server* server = new deflect::Server( 0 /* OS-chosen port */ ); server->connect( server, &deflect::Server::receivedSizeHints, - [&]( QString uri, deflect::SizeHints hints ) + [&]( const QString id, const deflect::SizeHints hints ) { - BOOST_CHECK( uri == testURI ); - BOOST_CHECK( hints == testHints ); + streamId = id; + sizeHints = hints; mutex.lock(); received.wakeAll(); mutex.unlock(); @@ -79,7 +87,7 @@ BOOST_AUTO_TEST_CASE( testSizeHintsReceivedByServer ) serverThread.start(); { - deflect::Stream stream( testURI.toStdString(), "localhost", + deflect::Stream stream( testStreamId.toStdString(), "localhost", server->serverPort()); BOOST_CHECK( stream.isConnected( )); stream.sendSizeHints( testHints ); @@ -89,6 +97,56 @@ BOOST_AUTO_TEST_CASE( testSizeHintsReceivedByServer ) received.wait( &mutex, 2000 /*ms*/ ); mutex.unlock(); + BOOST_CHECK_EQUAL( streamId.toStdString(), testStreamId.toStdString( )); + BOOST_CHECK( sizeHints == testHints ); + + serverThread.quit(); + serverThread.wait(); +} + +BOOST_AUTO_TEST_CASE( testRegisterForEventReceivedByServer ) +{ + QThread serverThread; + deflect::Server* server = new deflect::Server( 0 /* OS-chosen port */ ); + server->moveToThread( &serverThread ); + serverThread.connect( &serverThread, &QThread::finished, + server, &deflect::Server::deleteLater ); + serverThread.start(); + + QWaitCondition received; + QMutex mutex; + + QString streamId; + bool exclusiveBind = false; + deflect::EventReceiver* eventReceiver = nullptr; + + server->connect( server, &deflect::Server::registerToEvents, + [&]( const QString id, const bool exclusive, + deflect::EventReceiver* receiver ) + { + streamId = id; + exclusiveBind = exclusive; + eventReceiver = receiver; + mutex.lock(); + received.wakeAll(); + mutex.unlock(); + }); + + { + deflect::Stream stream( testStreamId.toStdString(), "localhost", + server->serverPort( )); + BOOST_REQUIRE( stream.isConnected( )); + // Just send an event to check the streamId received by the server + stream.registerForEvents( true ); + } + mutex.lock(); + received.wait( &mutex, 2000 /*ms*/ ); + mutex.unlock(); + + BOOST_CHECK_EQUAL( streamId.toStdString(), testStreamId.toStdString( )); + BOOST_CHECK_EQUAL( exclusiveBind, true ); + BOOST_CHECK( eventReceiver ); + serverThread.quit(); serverThread.wait(); } diff --git a/tests/cpp/StreamTests.cpp b/tests/cpp/StreamTests.cpp new file mode 100644 index 0000000..fee7e10 --- /dev/null +++ b/tests/cpp/StreamTests.cpp @@ -0,0 +1,119 @@ +/*********************************************************************/ +/* 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. */ +/*********************************************************************/ + +#define BOOST_TEST_MODULE Stream +#include +namespace ut = boost::unit_test; + +#include + +#include +#include +#include + +namespace +{ +const char* STREAM_ID_ENV_VAR = "DEFLECT_ID"; +const char* STREAM_HOST_ENV_VAR = "DEFLECT_HOST"; +} + +BOOST_AUTO_TEST_CASE( testParameterizedConstructorWithValues ) +{ + const deflect::Stream stream( "mystream", "somehost" ); + BOOST_CHECK_EQUAL( stream.getId(), "mystream" ); + BOOST_CHECK_EQUAL( stream.getHost(), "somehost" ); +} + +BOOST_AUTO_TEST_CASE( testDefaultConstructorReadsEnvironmentVariables ) +{ + qputenv( STREAM_ID_ENV_VAR, "mystream" ); + qputenv( STREAM_HOST_ENV_VAR, "somehost" ); + deflect::Stream stream; + BOOST_CHECK_EQUAL( stream.getId(), "mystream" ); + BOOST_CHECK_EQUAL( stream.getHost(), "somehost" ); + qunsetenv( STREAM_ID_ENV_VAR ); + qunsetenv( STREAM_HOST_ENV_VAR ); +} + +BOOST_AUTO_TEST_CASE( testParameterizedConstructorReadsEnvironmentVariables ) +{ + qputenv( STREAM_ID_ENV_VAR, "mystream" ); + qputenv( STREAM_HOST_ENV_VAR, "somehost" ); + const deflect::Stream stream( "", "" ); + BOOST_CHECK_EQUAL( stream.getId(), "mystream" ); + BOOST_CHECK_EQUAL( stream.getHost(), "somehost" ); + qunsetenv( STREAM_ID_ENV_VAR ); + qunsetenv( STREAM_HOST_ENV_VAR ); +} + +BOOST_AUTO_TEST_CASE( testWhenSteamHostNotProvidedThenThrow ) +{ + BOOST_REQUIRE( QString( qgetenv( STREAM_HOST_ENV_VAR )).isEmpty( )); + BOOST_CHECK_THROW( std::make_shared(), + std::runtime_error ); + BOOST_CHECK_THROW( deflect::Stream stream( "mystream", "" ), + std::runtime_error ); +} + +BOOST_AUTO_TEST_CASE( testWhenSteamHostProvidedThenNoThrow ) +{ + BOOST_CHECK_NO_THROW( deflect::Stream stream( "mystream", "somehost" )); + qputenv( STREAM_HOST_ENV_VAR, "somehost" ); + BOOST_CHECK_NO_THROW( std::make_shared( )); + BOOST_CHECK_NO_THROW( deflect::Stream stream( "mystream", "" )); + qunsetenv( STREAM_HOST_ENV_VAR ); +} + +BOOST_AUTO_TEST_CASE( testWhenNoSteamIdProvidedThenARandomOneIsGenerated ) +{ + BOOST_REQUIRE( QString( qgetenv( STREAM_ID_ENV_VAR )).isEmpty( )); + { + deflect::Stream stream( "", "somehost" ); + BOOST_CHECK( !stream.getId().empty( )); + BOOST_CHECK_NE( deflect::Stream( "", "host").getId(), + deflect::Stream( "", "host").getId( )); + } + { + qputenv( STREAM_HOST_ENV_VAR, "somehost" ); + deflect::Stream stream; + BOOST_CHECK( !stream.getId().empty( )); + BOOST_CHECK_NE( deflect::Stream().getId(), deflect::Stream().getId( )); + qunsetenv( STREAM_HOST_ENV_VAR ); + } +} diff --git a/tests/cpp/perf/benchmarkStreamer.cpp b/tests/cpp/perf/benchmarkStreamer.cpp index 46d5804..67ff5e7 100644 --- a/tests/cpp/perf/benchmarkStreamer.cpp +++ b/tests/cpp/perf/benchmarkStreamer.cpp @@ -109,8 +109,8 @@ struct BenchmarkOptions using namespace boost::program_options; desc.add_options() ("help", "produce help message") - ("name", value()->default_value( "BenchmarkStreamer" ), - "identifier for the stream") + ("id", value()->default_value( "BenchmarkStreamer" ), + "identifier for the stream") ("width", value()->default_value( 0 ), "width of the stream in pixel") ("height", value()->default_value( 0 ), @@ -119,7 +119,7 @@ struct BenchmarkOptions "number of frames") ("framerate", value()->default_value( 0 ), "framerate at which to send frames (default: unlimited)") - ("hostname", value()->default_value( "localhost" ), + ("host", value()->default_value( "localhost" ), "Target Deflect server host") ("compress", "compress segments using jpeg") ("precompute", "send precomputed segments (no encoding time)") @@ -147,12 +147,12 @@ struct BenchmarkOptions } getHelp = vm.count("help"); - name = vm["name"].as(); + id = vm["id"].as(); width = vm["width"].as(); height = vm["height"].as(); nframes = vm["nframes"].as(); framerate = vm["framerate"].as(); - hostname = vm["hostname"].as(); + host = vm["host"].as(); compress = vm.count("compress"); precompute = vm.count("precompute"); quality = vm["quality"].as(); @@ -161,11 +161,11 @@ struct BenchmarkOptions boost::program_options::options_description desc; bool getHelp; - std::string name; + std::string id; unsigned int width, height; unsigned int nframes; unsigned int framerate; - std::string hostname; + std::string host; bool compress; bool precompute; unsigned int quality; @@ -188,7 +188,7 @@ class Application public: explicit Application( const BenchmarkOptions& options ) : _options( options ) - , _stream( new deflect::Stream( options.name, options.hostname )) + , _stream( new deflect::Stream( options.id, options.host )) { generateNoiseImage( _options.width, _options.height ); generateJpegSegments();