2013-04-04 14:14:12 +00:00
/********************************************************************
KWin - the KDE window manager
This file is part of the KDE project .
Copyright ( C ) 2013 Martin Gräßlin < mgraesslin @ kde . org >
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# include "activities.h"
// KWin
# include "client.h"
# include "workspace.h"
// KDE
2014-03-17 15:24:10 +00:00
# include <KConfigGroup>
2014-02-04 08:36:15 +00:00
# include <kactivities/controller.h>
2013-04-04 14:14:12 +00:00
// Qt
# include <QtConcurrentRun>
# include <QDBusInterface>
# include <QDBusPendingCall>
# include <QFutureWatcher>
namespace KWin
{
2013-04-05 07:41:25 +00:00
KWIN_SINGLETON_FACTORY ( Activities )
2013-04-04 14:14:12 +00:00
Activities : : Activities ( QObject * parent )
: QObject ( parent )
, m_controller ( new KActivities : : Controller ( this ) )
{
2014-02-03 12:44:06 +00:00
connect ( m_controller , & KActivities : : Controller : : activityRemoved , this , & Activities : : slotRemoved ) ;
connect ( m_controller , & KActivities : : Controller : : activityRemoved , this , & Activities : : removed ) ;
connect ( m_controller , & KActivities : : Controller : : activityAdded , this , & Activities : : slotAdded ) ;
connect ( m_controller , & KActivities : : Controller : : activityAdded , this , & Activities : : added ) ;
connect ( m_controller , & KActivities : : Controller : : currentActivityChanged , this , & Activities : : slotCurrentChanged ) ;
2013-04-04 14:14:12 +00:00
}
Activities : : ~ Activities ( )
{
s_self = NULL ;
}
void Activities : : setCurrent ( const QString & activity )
{
m_controller - > setCurrentActivity ( activity ) ;
}
void Activities : : slotCurrentChanged ( const QString & newActivity )
{
if ( m_current = = newActivity ) {
return ;
}
m_previous = m_current ;
m_current = newActivity ;
emit currentChanged ( newActivity ) ;
}
void Activities : : slotAdded ( const QString & activity )
{
m_all < < activity ;
}
void Activities : : slotRemoved ( const QString & activity )
{
m_all . removeOne ( activity ) ;
foreach ( Client * client , Workspace : : self ( ) - > clientList ( ) ) {
client - > setOnActivity ( activity , false ) ;
}
//toss out any session data for it
2013-09-04 14:10:36 +00:00
KConfigGroup cg ( KSharedConfig : : openConfig ( ) , QString ( " SubSession: " ) + activity ) ;
2013-04-04 14:14:12 +00:00
cg . deleteGroup ( ) ;
}
void Activities : : toggleClientOnActivity ( Client * c , const QString & activity , bool dont_activate )
{
//int old_desktop = c->desktop();
bool was_on_activity = c - > isOnActivity ( activity ) ;
bool was_on_all = c - > isOnAllActivities ( ) ;
//note: all activities === no activities
bool enable = was_on_all | | ! was_on_activity ;
c - > setOnActivity ( activity , enable ) ;
if ( c - > isOnActivity ( activity ) = = was_on_activity & & c - > isOnAllActivities ( ) = = was_on_all ) // No change
return ;
Workspace * ws = Workspace : : self ( ) ;
if ( c - > isOnCurrentActivity ( ) ) {
if ( c - > wantsTabFocus ( ) & & options - > focusPolicyIsReasonable ( ) & &
! was_on_activity & & // for stickyness changes
//FIXME not sure if the line above refers to the correct activity
! dont_activate )
ws - > requestFocus ( c ) ;
else
ws - > restackClientUnderActive ( c ) ;
} else
ws - > raiseClient ( c ) ;
//notifyWindowDesktopChanged( c, old_desktop );
ClientList transients_stacking_order = ws - > ensureStackingOrder ( c - > transients ( ) ) ;
for ( ClientList : : ConstIterator it = transients_stacking_order . constBegin ( ) ;
it ! = transients_stacking_order . constEnd ( ) ;
+ + it )
toggleClientOnActivity ( * it , activity , dont_activate ) ;
ws - > updateClientArea ( ) ;
}
bool Activities : : start ( const QString & id )
{
Workspace * ws = Workspace : : self ( ) ;
if ( ws - > sessionSaving ( ) ) {
return false ; //ksmserver doesn't queue requests (yet)
}
if ( ! m_all . contains ( id ) ) {
return false ; //bogus id
}
ws - > loadSubSessionInfo ( id ) ;
QDBusInterface ksmserver ( " org.kde.ksmserver " , " /KSMServer " , " org.kde.KSMServerInterface " ) ;
if ( ksmserver . isValid ( ) ) {
ksmserver . asyncCall ( " restoreSubSession " , id ) ;
} else {
2013-09-02 11:14:39 +00:00
qDebug ( ) < < " couldn't get ksmserver interface " ;
2013-04-04 14:14:12 +00:00
return false ;
}
return true ;
}
bool Activities : : stop ( const QString & id )
{
if ( Workspace : : self ( ) - > sessionSaving ( ) ) {
return false ; //ksmserver doesn't queue requests (yet)
//FIXME what about session *loading*?
}
//ugly hack to avoid dbus deadlocks
update ( true , false ) ;
QMetaObject : : invokeMethod ( this , " reallyStop " , Qt : : QueuedConnection , Q_ARG ( QString , id ) ) ;
//then lie and assume it worked.
return true ;
}
void Activities : : reallyStop ( const QString & id )
{
Workspace * ws = Workspace : : self ( ) ;
if ( ws - > sessionSaving ( ) )
return ; //ksmserver doesn't queue requests (yet)
2013-09-02 11:14:39 +00:00
qDebug ( ) < < id ;
2013-04-04 14:14:12 +00:00
QSet < QByteArray > saveSessionIds ;
QSet < QByteArray > dontCloseSessionIds ;
const ClientList & clients = ws - > clientList ( ) ;
for ( ClientList : : const_iterator it = clients . constBegin ( ) ; it ! = clients . constEnd ( ) ; + + it ) {
const Client * c = ( * it ) ;
const QByteArray sessionId = c - > sessionId ( ) ;
if ( sessionId . isEmpty ( ) ) {
continue ; //TODO support old wm_command apps too?
}
2013-11-01 11:29:05 +00:00
//qDebug() << sessionId;
2013-04-04 14:14:12 +00:00
//if it's on the activity that's closing, it needs saving
//but if a process is on some other open activity, I don't wanna close it yet
//this is, of course, complicated by a process having many windows.
if ( c - > isOnAllActivities ( ) ) {
dontCloseSessionIds < < sessionId ;
continue ;
}
const QStringList activities = c - > activities ( ) ;
foreach ( const QString & activityId , activities ) {
if ( activityId = = id ) {
saveSessionIds < < sessionId ;
} else if ( m_running . contains ( activityId ) ) {
dontCloseSessionIds < < sessionId ;
}
}
}
ws - > storeSubSession ( id , saveSessionIds ) ;
QStringList saveAndClose ;
QStringList saveOnly ;
foreach ( const QByteArray & sessionId , saveSessionIds ) {
if ( dontCloseSessionIds . contains ( sessionId ) ) {
saveOnly < < sessionId ;
} else {
saveAndClose < < sessionId ;
}
}
2013-09-02 11:14:39 +00:00
qDebug ( ) < < " saveActivity " < < id < < saveAndClose < < saveOnly ;
2013-04-04 14:14:12 +00:00
//pass off to ksmserver
QDBusInterface ksmserver ( " org.kde.ksmserver " , " /KSMServer " , " org.kde.KSMServerInterface " ) ;
if ( ksmserver . isValid ( ) ) {
ksmserver . asyncCall ( " saveSubSession " , id , saveAndClose , saveOnly ) ;
} else {
2013-09-02 11:14:39 +00:00
qDebug ( ) < < " couldn't get ksmserver interface " ;
2013-04-04 14:14:12 +00:00
}
}
//BEGIN threaded activity list fetching
typedef QPair < QStringList * , QStringList > AssignedList ;
typedef QPair < QString , QStringList > CurrentAndList ;
static AssignedList
fetchActivityList ( KActivities : : Controller * controller , QStringList * target , bool running ) // could be member function, but actually it's much simpler this way
{
2014-02-03 12:40:47 +00:00
return AssignedList ( target , running ? controller - > activities ( KActivities : : Info : : Running ) :
controller - > activities ( ) ) ;
2013-04-04 14:14:12 +00:00
}
static CurrentAndList
fetchActivityListAndCurrent ( KActivities : : Controller * controller )
{
2014-02-03 12:40:47 +00:00
QStringList l = controller - > activities ( ) ;
2013-04-04 14:14:12 +00:00
QString c = controller - > currentActivity ( ) ;
return CurrentAndList ( c , l ) ;
}
void Activities : : update ( bool running , bool updateCurrent , QObject * target , QString slot )
{
if ( updateCurrent ) {
QFutureWatcher < CurrentAndList > * watcher = new QFutureWatcher < CurrentAndList > ;
connect ( watcher , SIGNAL ( finished ( ) ) , SLOT ( handleReply ( ) ) ) ;
if ( ! slot . isEmpty ( ) ) {
watcher - > setProperty ( " activityControllerCallback " , slot ) ; // "activity reply trigger"
watcher - > setProperty ( " activityControllerCallbackTarget " , qVariantFromValue ( ( void * ) target ) ) ;
}
watcher - > setFuture ( QtConcurrent : : run ( fetchActivityListAndCurrent , m_controller ) ) ;
} else {
QFutureWatcher < AssignedList > * watcher = new QFutureWatcher < AssignedList > ;
connect ( watcher , SIGNAL ( finished ( ) ) , SLOT ( handleReply ( ) ) ) ;
if ( ! slot . isEmpty ( ) ) {
watcher - > setProperty ( " activityControllerCallback " , slot ) ; // "activity reply trigger"
watcher - > setProperty ( " activityControllerCallbackTarget " , qVariantFromValue ( ( void * ) target ) ) ;
}
QStringList * target = running ? & m_running : & m_all ;
watcher - > setFuture ( QtConcurrent : : run ( fetchActivityList , m_controller , target , running ) ) ;
}
}
void Activities : : handleReply ( )
{
QObject * watcherObject = 0 ;
if ( QFutureWatcher < AssignedList > * watcher = dynamic_cast < QFutureWatcher < AssignedList > * > ( sender ( ) ) ) {
// we carry over the to-be-updated StringList member as pointer in the threaded return
* ( watcher - > result ( ) . first ) = watcher - > result ( ) . second ;
watcherObject = watcher ;
}
if ( ! watcherObject ) {
if ( QFutureWatcher < CurrentAndList > * watcher = dynamic_cast < QFutureWatcher < CurrentAndList > * > ( sender ( ) ) ) {
m_all = watcher - > result ( ) . second ;
slotCurrentChanged ( watcher - > result ( ) . first ) ;
watcherObject = watcher ;
}
}
if ( watcherObject ) {
QString slot = watcherObject - > property ( " activityControllerCallback " ) . toString ( ) ;
QObject * target = static_cast < QObject * > ( watcherObject - > property ( " activityControllerCallbackTarget " ) . value < void * > ( ) ) ;
watcherObject - > deleteLater ( ) ; // has done it's job
if ( ! slot . isEmpty ( ) )
QMetaObject : : invokeMethod ( target , slot . toAscii ( ) . data ( ) , Qt : : DirectConnection ) ;
}
}
//END threaded activity list fetching
} // namespace