diff --git a/apps/DesktopStreamer/DesktopWindowsModel.h b/apps/DesktopStreamer/DesktopWindowsModel.h index c9c3a6b..fd28f37 100644 --- a/apps/DesktopStreamer/DesktopWindowsModel.h +++ b/apps/DesktopStreamer/DesktopWindowsModel.h @@ -43,7 +43,8 @@ class DesktopWindowsModel : public QAbstractListModel enum DataRole { ROLE_PIXMAP = Qt::UserRole, - ROLE_RECT + ROLE_RECT, + ROLE_PID }; /** @internal */ @@ -52,6 +53,24 @@ class DesktopWindowsModel : public QAbstractListModel /** @internal */ void removeApplication( void* app ); + /** + * Check if the application corresponding to the specified PID is currently + * active + * + * @param pid the application process id + * @return true if the application with the PID specified is currently + * active + */ + static bool isActive( int pid ); + + /** + * Activate the application corresponding to the specified PID. This will + * send the application to the front + * + * @param pid the application process id + */ + static void activate( int pid ); + private: class Impl; std::unique_ptr< Impl > _impl; diff --git a/apps/DesktopStreamer/DesktopWindowsModel.mm b/apps/DesktopStreamer/DesktopWindowsModel.mm index d984e92..ad998c2 100644 --- a/apps/DesktopStreamer/DesktopWindowsModel.mm +++ b/apps/DesktopStreamer/DesktopWindowsModel.mm @@ -172,18 +172,28 @@ - (void)setParent:(DesktopWindowsModel*)parent_ parent = parent_; } +- (bool)isCurrent:(NSRunningApplication*)app_ +{ + NSRunningApplication* current = [NSRunningApplication currentApplication]; + return [app_ processIdentifier] == [current processIdentifier]; +} + - (void)newApplication:(NSNotification*) notification { NSRunningApplication* app = [[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"]; - parent->addApplication( app ); + + if( ![self isCurrent:app] ) + parent->addApplication( app ); } - (void)closedApplication:(NSNotification*) notification { NSRunningApplication* app = [[notification userInfo] objectForKey:@"NSWorkspaceApplicationKey"]; - parent->removeApplication( app ); + + if( ![self isCurrent:app] ) + parent->removeApplication( app ); } @end @@ -209,7 +219,8 @@ - (void)closedApplication:(NSNotification*) notification { APPNAME, WINDOWID, - WINDOWIMAGE + WINDOWIMAGE, + PID }; void addApplication( NSRunningApplication* app ) @@ -260,8 +271,9 @@ void reloadData() kCGNullWindowID ); _data.clear(); - _data.push_back( std::make_tuple( "Desktop", 0, - getPreviewPixmap( QApplication::primaryScreen()->grabWindow( 0 )))); + const QPixmap preview = + getPreviewPixmap( QApplication::primaryScreen()->grabWindow(0)); + _data.push_back( std::make_tuple( "Desktop", 0, preview, 0 )); for( NSRunningApplication* app in runningApplications ) _addApplication( app, windowList ); @@ -271,7 +283,8 @@ void reloadData() } DesktopWindowsModel& _parent; - typedef std::vector< std::tuple< QString, CGWindowID, QPixmap > > Data; + typedef std::vector< std::tuple< + QString, CGWindowID, QPixmap, int > > Data; Data _data; AppObserver* _observer; @@ -308,9 +321,8 @@ bool _addApplication( NSRunningApplication* app, CFArrayRef windowList ) qDebug() << "Ignoring" << appName << "-" << windowName; continue; } - _data.push_back( std::make_tuple( appName, windowID, - getPreviewPixmap( getWindowPixmap( windowID )))); + getPreviewPixmap( getWindowPixmap( windowID )), pid )); gotOne = true; } return gotOne; @@ -345,11 +357,28 @@ bool _addApplication( NSRunningApplication* app, CFArrayRef windowList ) case ROLE_RECT: return getWindowRect( std::get< Impl::WINDOWID >( data )); + case ROLE_PID: + return std::get< Impl::PID >( data ); + default: return QVariant(); } } +bool DesktopWindowsModel::isActive( const int pid ) +{ + return [[NSRunningApplication + runningApplicationWithProcessIdentifier: pid] isActive]; +} + +void DesktopWindowsModel::activate( const int pid ) +{ + NSRunningApplication* app = [NSRunningApplication + runningApplicationWithProcessIdentifier: pid]; + [app activateWithOptions: NSApplicationActivateAllWindows | + NSApplicationActivateIgnoringOtherApps]; +} + void DesktopWindowsModel::addApplication( void* app ) { _impl->addApplication( (NSRunningApplication*)app ); diff --git a/apps/DesktopStreamer/MainWindow.cpp b/apps/DesktopStreamer/MainWindow.cpp index 9da1cc4..b465d4f 100644 --- a/apps/DesktopStreamer/MainWindow.cpp +++ b/apps/DesktopStreamer/MainWindow.cpp @@ -201,7 +201,10 @@ void MainWindow::_updateStreams() const std::string streamId = std::to_string( ++_streamID ) + " " + appName + " - " + _streamIdLineEdit->text().toStdString(); - StreamPtr stream( new Stream( *this, index, streamId, host )); + const int pid = index.isValid() ? + _listView->model()->data( index, + DesktopWindowsModel::ROLE_PID).toInt(): 0; + StreamPtr stream( new Stream( *this, index, streamId, host, pid )); if( !stream->isConnected( )) { diff --git a/apps/DesktopStreamer/Stream.cpp b/apps/DesktopStreamer/Stream.cpp index 173b663..5dde175 100644 --- a/apps/DesktopStreamer/Stream.cpp +++ b/apps/DesktopStreamer/Stream.cpp @@ -52,29 +52,31 @@ #endif #include #include +#include #define CURSOR_IMAGE_FILE ":/cursor.png" #define CURSOR_IMAGE_SIZE 20 -Stream::Stream( const MainWindow& parent, const QPersistentModelIndex window, - const std::string& id, const std::string& host ) - : deflect::Stream( id, host ) +class Stream::Impl +{ +public: + Impl( Stream& stream, const MainWindow& parent, + const QPersistentModelIndex window, const int pid ) + : _stream( stream ) , _parent( parent ) , _window( window ) , _cursor( QImage( CURSOR_IMAGE_FILE ).scaled( CURSOR_IMAGE_SIZE * parent.devicePixelRatio(), CURSOR_IMAGE_SIZE * parent.devicePixelRatio(), Qt::KeepAspectRatio )) -{} - -Stream::~Stream() -{} + , _pid( pid ) + {} -bool Stream::processEvents( const bool interact ) +bool processEvents( const bool interact ) { - while( isRegisteredForEvents() && hasEvent( )) + while( _stream.isRegisteredForEvents() && _stream.hasEvent( )) { - deflect::Event event = getEvent(); + deflect::Event event = _stream.getEvent(); if( event.type == deflect::Event::EVT_CLOSE ) return false; @@ -115,7 +117,7 @@ bool Stream::processEvents( const bool interact ) return true; } -std::string Stream::update() +std::string update() { QPixmap pixmap; @@ -123,9 +125,9 @@ std::string Stream::update() if( !_window.isValid( )) return "Window does not exist anymore"; + const QAbstractItemModel* model = _parent.getItemModel(); if( _window.row() > 0 ) { - const QAbstractItemModel* model = _parent.getItemModel(); pixmap = model->data( _window, DesktopWindowsModel::ROLE_PIXMAP ).value< QPixmap >(); _windowRect = model->data( _window, @@ -143,14 +145,19 @@ std::string Stream::update() return "Got no pixmap for desktop or window"; QImage image = pixmap.toImage(); - - // render mouse cursor - const QPoint mousePos = ( QCursor::pos() - _windowRect.topLeft( )) * - _parent.devicePixelRatio() - - QPoint( _cursor.width()/2, _cursor.height()/2 ); - QPainter painter( &image ); - painter.drawImage( mousePos, _cursor ); - painter.end(); // Make sure to release the QImage before using it +#ifdef DEFLECT_USE_QT5MACEXTRAS + // render mouse cursor only on active window and full desktop streams + if( DesktopWindowsModel::isActive( _pid ) || + model->data( _window, Qt::DisplayRole ) == "Desktop" ) +#endif + { + const QPoint mousePos = ( QCursor::pos() - _windowRect.topLeft( )) * + _parent.devicePixelRatio() - + QPoint( _cursor.width()/2, _cursor.height()/2 ); + QPainter painter( &image ); + painter.drawImage( mousePos, _cursor ); + painter.end(); // Make sure to release the QImage before using it + } if( image == _image ) return std::string(); // OPT: Image is unchanged @@ -162,46 +169,66 @@ std::string Stream::update() deflect::BGRA ); deflectImage.compressionPolicy = deflect::COMPRESSION_ON; - if( !send( deflectImage ) || !finishFrame( )) + if( !_stream.send( deflectImage ) || !_stream.finishFrame( )) return "Streaming failure, connection closed"; return std::string(); } #ifdef __APPLE__ -void sendMouseEvent( const CGEventType type, const CGMouseButton button, - const CGPoint point ) +void _sendMouseEvent( const CGEventType type, const CGMouseButton button, + const CGPoint point ) { CGEventRef event = CGEventCreateMouseEvent( 0, type, point, button ); CGEventSetType( event, type ); - CGEventPost( kCGHIDEventTap, event ); - CFRelease( event ); + +#ifdef DEFLECT_USE_QT5MACEXTRAS + // If the destination app is not active, store the event in a queue and + // consume it after it's been activated (next iteration of main run loop) + if( !DesktopWindowsModel::isActive( _pid )) + { + DesktopWindowsModel::activate( _pid ); + EventQueue().swap( _events ); // empty the queue + _events.push( event ); + return; + } +#endif + _events.push( event ); + + while( !_events.empty( )) + { + event = _events.front(); + CGEventPost( kCGHIDEventTap, event ); + CFRelease( event ); + _events.pop(); + } + return; } -void Stream::_sendMousePressEvent( const float x, const float y ) +void _sendMousePressEvent( const float x, const float y ) { CGPoint point; point.x = _windowRect.topLeft().x() + x * _windowRect.width(); point.y = _windowRect.topLeft().y() + y * _windowRect.height(); - sendMouseEvent( kCGEventLeftMouseDown, kCGMouseButtonLeft, point ); + _sendMouseEvent( kCGEventLeftMouseDown, kCGMouseButtonLeft, point ); } -void Stream::_sendMouseMoveEvent( const float x, const float y ) +void _sendMouseMoveEvent( const float x, const float y ) { CGPoint point; point.x = _windowRect.topLeft().x() + x * _windowRect.width(); point.y = _windowRect.topLeft().y() + y * _windowRect.height(); - sendMouseEvent( kCGEventMouseMoved, kCGMouseButtonLeft, point ); + _sendMouseEvent( kCGEventMouseMoved, kCGMouseButtonLeft, point ); } -void Stream::_sendMouseReleaseEvent( const float x, const float y ) +void _sendMouseReleaseEvent( const float x, const float y ) { CGPoint point; point.x = _windowRect.topLeft().x() + x * _windowRect.width(); point.y = _windowRect.topLeft().y() + y * _windowRect.height(); - sendMouseEvent( kCGEventLeftMouseUp, kCGMouseButtonLeft, point ); + _sendMouseEvent( kCGEventLeftMouseUp, kCGMouseButtonLeft, point ); } -void Stream::_sendMouseDoubleClickEvent( const float x, const float y ) +void _sendMouseDoubleClickEvent( const float x, const float y ) { CGPoint point; point.x = _windowRect.topLeft().x() + x * _windowRect.width(); @@ -223,8 +250,45 @@ void Stream::_sendMouseDoubleClickEvent( const float x, const float y ) CFRelease( event ); } #else -void Stream::_sendMousePressEvent( const float, const float ) {} -void Stream::_sendMouseMoveEvent( const float, const float ) {} -void Stream::_sendMouseReleaseEvent( const float, const float ) {} -void Stream::_sendMouseDoubleClickEvent( const float, const float ) {} +void _sendMousePressEvent( const float, const float ) {} +void _sendMouseMoveEvent( const float, const float ) {} +void _sendMouseReleaseEvent( const float, const float ) {} +void _sendMouseDoubleClickEvent( const float, const float ) {} +#endif + + Stream& _stream; + const MainWindow& _parent; + const QPersistentModelIndex _window; + QRect _windowRect; // position on host in non-retina coordinates + const QImage _cursor; + QImage _image; + const int _pid; +#ifdef DEFLECT_USE_QT5MACEXTRAS + typedef std::queue< CGEventRef > EventQueue; + EventQueue _events; #endif +}; + +Stream::Stream( const MainWindow& parent, const QPersistentModelIndex window, + const std::string& name, const std::string& host, const int pid) + : deflect::Stream( name, host ) + , _impl( new Impl( *this, parent, window, pid )) +{} + +Stream::~Stream() +{} + +std::string Stream::update() +{ + return _impl->update(); +} + +bool Stream::processEvents( const bool interact ) +{ + return _impl->processEvents( interact ); +} + +const QPersistentModelIndex& Stream::getIndex() const +{ + return _impl->_window; +} diff --git a/apps/DesktopStreamer/Stream.h b/apps/DesktopStreamer/Stream.h index 8ecc094..c41ee99 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& id, const std::string& host ); + const std::string& id, const std::string& host, const int pid = 0 ); ~Stream(); /** @@ -69,22 +69,14 @@ class Stream : public deflect::Stream */ bool processEvents( bool interact ); - const QPersistentModelIndex& getIndex() const { return _window; } + const QPersistentModelIndex& getIndex() const; private: - const MainWindow& _parent; - const QPersistentModelIndex _window; - QRect _windowRect; // position on host in non-retina coordinates - const QImage _cursor; - QImage _image; - Stream( const Stream& ) = delete; Stream( Stream&& ) = delete; - void _sendMousePressEvent( float x, float y ); - void _sendMouseMoveEvent( float x, float y ); - void _sendMouseReleaseEvent( float x, float y ); - void _sendMouseDoubleClickEvent( float x, float y ); + class Impl; + std::unique_ptr< Impl > _impl; }; #endif diff --git a/doc/Changelog.md b/doc/Changelog.md index bc183ce..a612073 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -8,13 +8,17 @@ Changelog {#Changelog} * [102](https://github.com/BlueBrain/Deflect/pull/102): DeflectQt: Continue rendering & streaming after updates for a while to compensate for running animations, fix spurious missing event handling +* [101](https://github.com/BlueBrain/Deflect/pull/101): + DesktopStreamer: windows that are streamed independently are activated + (i.e. sent to the foreground) before applying an interaction event. The mouse + cursor is now rendered only on active windows or desktop. * [100](https://github.com/BlueBrain/Deflect/pull/100): 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): +* [97](https://github.com/BlueBrain/Deflect/pull/97): DesktopStreamer: the list of windows available for streaming is also updated after a window has been hidden or unhidden. * [94](https://github.com/BlueBrain/Deflect/pull/94):