Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion apps/DesktopStreamer/DesktopWindowsModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ class DesktopWindowsModel : public QAbstractListModel
enum DataRole
{
ROLE_PIXMAP = Qt::UserRole,
ROLE_RECT
ROLE_RECT,
ROLE_PID
};

/** @internal */
Expand All @@ -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;
Expand Down
45 changes: 37 additions & 8 deletions apps/DesktopStreamer/DesktopWindowsModel.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -209,7 +219,8 @@ - (void)closedApplication:(NSNotification*) notification
{
APPNAME,
WINDOWID,
WINDOWIMAGE
WINDOWIMAGE,
PID
};

void addApplication( NSRunningApplication* app )
Expand Down Expand Up @@ -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 );
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
5 changes: 4 additions & 1 deletion apps/DesktopStreamer/MainWindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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( ))
{
Expand Down
136 changes: 100 additions & 36 deletions apps/DesktopStreamer/Stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,29 +52,31 @@
#endif
#include <QPainter>
#include <QScreen>
#include <queue>

#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;
Expand Down Expand Up @@ -115,17 +117,17 @@ bool Stream::processEvents( const bool interact )
return true;
}

std::string Stream::update()
std::string update()
{
QPixmap pixmap;

#ifdef DEFLECT_USE_QT5MACEXTRAS
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,
Expand All @@ -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
Expand All @@ -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();
Expand All @@ -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;
}
Loading