summaryrefslogtreecommitdiffstats
path: root/kwin/sm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kwin/sm.cpp')
-rw-r--r--kwin/sm.cpp444
1 files changed, 444 insertions, 0 deletions
diff --git a/kwin/sm.cpp b/kwin/sm.cpp
new file mode 100644
index 000000000..37b9526c7
--- /dev/null
+++ b/kwin/sm.cpp
@@ -0,0 +1,444 @@
+/*****************************************************************
+ KWin - the KDE window manager
+ This file is part of the KDE project.
+
+Copyright (C) 1999, 2000 Matthias Ettrich <[email protected]>
+Copyright (C) 2003 Lubos Lunak <[email protected]>
+
+You can Freely distribute this program under the GNU General Public
+License. See the file "COPYING" for the exact licensing terms.
+******************************************************************/
+
+#include "sm.h"
+
+#include <qsocketnotifier.h>
+#include <qsessionmanager.h>
+#include <kdebug.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <pwd.h>
+#include <fixx11h.h>
+#include <kconfig.h>
+#include <kglobal.h>
+
+#include "workspace.h"
+#include "client.h"
+
+namespace KWinInternal
+{
+
+bool SessionManaged::saveState( QSessionManager& sm )
+ {
+ // If the session manager is ksmserver, save stacking
+ // order, active window, active desktop etc. in phase 1,
+ // as ksmserver assures no interaction will be done
+ // before the WM finishes phase 1. Saving in phase 2 is
+ // too late, as possible user interaction may change some things.
+ // Phase2 is still needed though (ICCCM 5.2)
+ char* sm_vendor = SmcVendor( static_cast< SmcConn >( sm.handle()));
+ bool ksmserver = qstrcmp( sm_vendor, "KDE" ) == 0;
+ free( sm_vendor );
+ if ( !sm.isPhase2() )
+ {
+ Workspace::self()->sessionSaveStarted();
+ if( ksmserver ) // save stacking order etc. before "save file?" etc. dialogs change it
+ Workspace::self()->storeSession( kapp->sessionConfig(), SMSavePhase0 );
+ sm.release(); // Qt doesn't automatically release in this case (bug?)
+ sm.requestPhase2();
+ return true;
+ }
+ Workspace::self()->storeSession( kapp->sessionConfig(), ksmserver ? SMSavePhase2 : SMSavePhase2Full );
+ kapp->sessionConfig()->sync();
+ return true;
+ }
+
+// I bet this is broken, just like everywhere else in KDE
+bool SessionManaged::commitData( QSessionManager& sm )
+ {
+ if ( !sm.isPhase2() )
+ Workspace::self()->sessionSaveStarted();
+ return true;
+ }
+
+// Workspace
+
+/*!
+ Stores the current session in the config file
+
+ \sa loadSessionInfo()
+ */
+void Workspace::storeSession( KConfig* config, SMSavePhase phase )
+ {
+ config->setGroup("Session" );
+ int count = 0;
+ int active_client = -1;
+ for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it)
+ {
+ Client* c = (*it);
+ QCString sessionId = c->sessionId();
+ QCString wmCommand = c->wmCommand();
+ if ( sessionId.isEmpty() )
+ // remember also applications that are not XSMP capable
+ // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
+ if ( wmCommand.isEmpty() )
+ continue;
+ count++;
+ if( c->isActive())
+ active_client = count;
+ QString n = QString::number(count);
+ if( phase == SMSavePhase2 || phase == SMSavePhase2Full )
+ {
+ config->writeEntry( QString("sessionId")+n, sessionId.data() );
+ config->writeEntry( QString("windowRole")+n, c->windowRole().data() );
+ config->writeEntry( QString("wmCommand")+n, wmCommand.data() );
+ config->writeEntry( QString("wmClientMachine")+n, c->wmClientMachine( true ).data() );
+ config->writeEntry( QString("resourceName")+n, c->resourceName().data() );
+ config->writeEntry( QString("resourceClass")+n, c->resourceClass().data() );
+ config->writeEntry( QString("geometry")+n, QRect( c->calculateGravitation(TRUE), c->clientSize() ) ); // FRAME
+ config->writeEntry( QString("restore")+n, c->geometryRestore() );
+ config->writeEntry( QString("fsrestore")+n, c->geometryFSRestore() );
+ config->writeEntry( QString("maximize")+n, (int) c->maximizeMode() );
+ config->writeEntry( QString("fullscreen")+n, (int) c->fullScreenMode() );
+ config->writeEntry( QString("desktop")+n, c->desktop() );
+ // the config entry is called "iconified" for back. comp. reasons
+ // (kconf_update script for updating session files would be too complicated)
+ config->writeEntry( QString("iconified")+n, c->isMinimized() );
+ // the config entry is called "sticky" for back. comp. reasons
+ config->writeEntry( QString("sticky")+n, c->isOnAllDesktops() );
+ config->writeEntry( QString("shaded")+n, c->isShade() );
+ // the config entry is called "staysOnTop" for back. comp. reasons
+ config->writeEntry( QString("staysOnTop")+n, c->keepAbove() );
+ config->writeEntry( QString("keepBelow")+n, c->keepBelow() );
+ config->writeEntry( QString("skipTaskbar")+n, c->skipTaskbar( true ) );
+ config->writeEntry( QString("skipPager")+n, c->skipPager() );
+ config->writeEntry( QString("userNoBorder")+n, c->isUserNoBorder() );
+ config->writeEntry( QString("windowType")+n, windowTypeToTxt( c->windowType()));
+ config->writeEntry( QString("shortcut")+n, c->shortcut().toStringInternal());
+ }
+ }
+ // TODO store also stacking order
+ if( phase == SMSavePhase0 )
+ {
+ // it would be much simpler to save these values to the config file,
+ // but both Qt and KDE treat phase1 and phase2 separately,
+ // which results in different sessionkey and different config file :(
+ session_active_client = active_client;
+ session_desktop = currentDesktop();
+ }
+ else if( phase == SMSavePhase2 )
+ {
+ config->writeEntry( "count", count );
+ config->writeEntry( "active", session_active_client );
+ config->writeEntry( "desktop", session_desktop );
+ }
+ else // SMSavePhase2Full
+ {
+ config->writeEntry( "count", count );
+ config->writeEntry( "active", session_active_client );
+ config->writeEntry( "desktop", currentDesktop());
+ }
+ }
+
+
+/*!
+ Loads the session information from the config file.
+
+ \sa storeSession()
+ */
+void Workspace::loadSessionInfo()
+ {
+ session.clear();
+ KConfig* config = kapp->sessionConfig();
+ config->setGroup("Session" );
+ int count = config->readNumEntry( "count" );
+ int active_client = config->readNumEntry( "active" );
+ for ( int i = 1; i <= count; i++ )
+ {
+ QString n = QString::number(i);
+ SessionInfo* info = new SessionInfo;
+ session.append( info );
+ info->sessionId = config->readEntry( QString("sessionId")+n ).latin1();
+ info->windowRole = config->readEntry( QString("windowRole")+n ).latin1();
+ info->wmCommand = config->readEntry( QString("wmCommand")+n ).latin1();
+ info->wmClientMachine = config->readEntry( QString("wmClientMachine")+n ).latin1();
+ info->resourceName = config->readEntry( QString("resourceName")+n ).latin1();
+ info->resourceClass = config->readEntry( QString("resourceClass")+n ).lower().latin1();
+ info->geometry = config->readRectEntry( QString("geometry")+n );
+ info->restore = config->readRectEntry( QString("restore")+n );
+ info->fsrestore = config->readRectEntry( QString("fsrestore")+n );
+ info->maximized = config->readNumEntry( QString("maximize")+n, 0 );
+ info->fullscreen = config->readNumEntry( QString("fullscreen")+n, 0 );
+ info->desktop = config->readNumEntry( QString("desktop")+n, 0 );
+ info->minimized = config->readBoolEntry( QString("iconified")+n, FALSE );
+ info->onAllDesktops = config->readBoolEntry( QString("sticky")+n, FALSE );
+ info->shaded = config->readBoolEntry( QString("shaded")+n, FALSE );
+ info->keepAbove = config->readBoolEntry( QString("staysOnTop")+n, FALSE );
+ info->keepBelow = config->readBoolEntry( QString("keepBelow")+n, FALSE );
+ info->skipTaskbar = config->readBoolEntry( QString("skipTaskbar")+n, FALSE );
+ info->skipPager = config->readBoolEntry( QString("skipPager")+n, FALSE );
+ info->userNoBorder = config->readBoolEntry( QString("userNoBorder")+n, FALSE );
+ info->windowType = txtToWindowType( config->readEntry( QString("windowType")+n ).latin1());
+ info->shortcut = config->readEntry( QString("shortcut")+n );
+ info->active = ( active_client == i );
+ }
+ }
+
+/*!
+ Returns a SessionInfo for client \a c. The returned session
+ info is removed from the storage. It's up to the caller to delete it.
+
+ This function is called when a new window is mapped and must be managed.
+ We try to find a matching entry in the session.
+
+ May return 0 if there's no session info for the client.
+ */
+SessionInfo* Workspace::takeSessionInfo( Client* c )
+ {
+ SessionInfo *realInfo = 0;
+ QCString sessionId = c->sessionId();
+ QCString windowRole = c->windowRole();
+ QCString wmCommand = c->wmCommand();
+ QCString wmClientMachine = c->wmClientMachine( true );
+ QCString resourceName = c->resourceName();
+ QCString resourceClass = c->resourceClass();
+
+ // First search ``session''
+ if (! sessionId.isEmpty() )
+ {
+ // look for a real session managed client (algorithm suggested by ICCCM)
+ for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() )
+ if ( info->sessionId == sessionId && sessionInfoWindowTypeMatch( c, info ))
+ {
+ if ( ! windowRole.isEmpty() )
+ {
+ if ( info->windowRole == windowRole )
+ realInfo = session.take();
+ }
+ else
+ {
+ if ( info->windowRole.isEmpty() &&
+ info->resourceName == resourceName &&
+ info->resourceClass == resourceClass )
+ realInfo = session.take();
+ }
+ }
+ }
+ else
+ {
+ // look for a sessioninfo with matching features.
+ for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() )
+ if ( info->resourceName == resourceName &&
+ info->resourceClass == resourceClass &&
+ info->wmClientMachine == wmClientMachine &&
+ sessionInfoWindowTypeMatch( c, info ))
+ if ( wmCommand.isEmpty() || info->wmCommand == wmCommand )
+ realInfo = session.take();
+ }
+
+ return realInfo;
+ }
+
+bool Workspace::sessionInfoWindowTypeMatch( Client* c, SessionInfo* info )
+ {
+ if( info->windowType == -2 )
+ { // undefined (not really part of NET::WindowType)
+ return !c->isSpecialWindow();
+ }
+ return info->windowType == c->windowType();
+ }
+
+// maybe needed later
+#if 0
+// KMainWindow's without name() given have WM_WINDOW_ROLE in the form
+// of <appname>-mainwindow#<number>
+// when comparing them for fake session info, it's probably better to check
+// them without the trailing number
+bool Workspace::windowRoleMatch( const QCString& role1, const QCString& role2 )
+ {
+ if( role1.isEmpty() && role2.isEmpty())
+ return true;
+ int pos1 = role1.find( '#' );
+ int pos2 = role2.find( '#' );
+ bool ret;
+ if( pos1 < 0 || pos2 < 0 || pos1 != pos2 )
+ ret = role1 == role2;
+ else
+ ret = qstrncmp( role1, role2, pos1 ) == 0;
+ kdDebug() << "WR:" << role1 << ":" << pos1 << ":" << role2 << ":" << pos2 << ":::" << ret << endl;
+ return ret;
+ }
+#endif
+
+static const char* const window_type_names[] =
+ {
+ "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
+ "Override", "TopMenu", "Utility", "Splash"
+ };
+ // change also the two functions below when adding new entries
+
+const char* Workspace::windowTypeToTxt( NET::WindowType type )
+ {
+ if( type >= NET::Unknown && type <= NET::Splash )
+ return window_type_names[ type + 1 ]; // +1 (unknown==-1)
+ if( type == -2 ) // undefined (not really part of NET::WindowType)
+ return "Undefined";
+ kdFatal() << "Unknown Window Type" << endl;
+ return NULL;
+ }
+
+NET::WindowType Workspace::txtToWindowType( const char* txt )
+ {
+ for( int i = NET::Unknown;
+ i <= NET::Splash;
+ ++i )
+ if( qstrcmp( txt, window_type_names[ i + 1 ] ) == 0 ) // +1
+ return static_cast< NET::WindowType >( i );
+ return static_cast< NET::WindowType >( -2 ); // undefined
+ }
+
+
+
+
+// KWin's focus stealing prevention causes problems with user interaction
+// during session save, as it prevents possible dialogs from getting focus.
+// Therefore it's temporarily disabled during session saving. Start of
+// session saving can be detected in SessionManaged::saveState() above,
+// but Qt doesn't have API for saying when session saved finished (either
+// successfully, or was cancelled). Therefore, create another connection
+// to session manager, that will provide this information.
+// Similarly the remember feature of window-specific settings should be disabled
+// during KDE shutdown when windows may move e.g. because of Kicker going away
+// (struts changing). When session saving starts, it can be cancelled, in which
+// case the shutdown_cancelled callback is invoked, or it's a checkpoint that
+// is immediatelly followed by save_complete, or finally it's a shutdown that
+// is immediatelly followed by die callback. So getting save_yourself with shutdown
+// set disables window-specific settings remembering, getting shutdown_cancelled
+// re-enables, otherwise KWin will go away after die.
+static void save_yourself( SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool )
+ {
+ SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
+ if( conn_P != session->connection())
+ return;
+ if( shutdown )
+ Workspace::self()->disableRulesUpdates( true );
+ SmcSaveYourselfDone( conn_P, True );
+ }
+
+static void die( SmcConn conn_P, SmPointer ptr )
+ {
+ SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
+ if( conn_P != session->connection())
+ return;
+ // session->saveDone(); we will quit anyway
+ session->close();
+ }
+
+static void save_complete( SmcConn conn_P, SmPointer ptr )
+ {
+ SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
+ if( conn_P != session->connection())
+ return;
+ session->saveDone();
+ }
+
+static void shutdown_cancelled( SmcConn conn_P, SmPointer ptr )
+ {
+ SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
+ if( conn_P != session->connection())
+ return;
+ Workspace::self()->disableRulesUpdates( false ); // re-enable
+ // no need to differentiate between successful finish and cancel
+ session->saveDone();
+ }
+
+void SessionSaveDoneHelper::saveDone()
+ {
+ Workspace::self()->sessionSaveDone();
+ }
+
+SessionSaveDoneHelper::SessionSaveDoneHelper()
+ {
+ SmcCallbacks calls;
+ calls.save_yourself.callback = save_yourself;
+ calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this);
+ calls.die.callback = die;
+ calls.die.client_data = reinterpret_cast< SmPointer >(this);
+ calls.save_complete.callback = save_complete;
+ calls.save_complete.client_data = reinterpret_cast< SmPointer >(this);
+ calls.shutdown_cancelled.callback = shutdown_cancelled;
+ calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this);
+ char* id = NULL;
+ char err[ 11 ];
+ conn = SmcOpenConnection( NULL, 0, 1, 0,
+ SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask
+ | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err );
+ if( id != NULL )
+ free( id );
+ if( conn == NULL )
+ return; // no SM
+ // set the required properties, mostly dummy values
+ SmPropValue propvalue[ 5 ];
+ SmProp props[ 5 ];
+ propvalue[ 0 ].length = sizeof( int );
+ int value0 = SmRestartNever; // so that this extra SM connection doesn't interfere
+ propvalue[ 0 ].value = &value0;
+ props[ 0 ].name = const_cast< char* >( SmRestartStyleHint );
+ props[ 0 ].type = const_cast< char* >( SmCARD8 );
+ props[ 0 ].num_vals = 1;
+ props[ 0 ].vals = &propvalue[ 0 ];
+ struct passwd* entry = getpwuid( geteuid() );
+ propvalue[ 1 ].length = entry != NULL ? strlen( entry->pw_name ) : 0;
+ propvalue[ 1 ].value = (SmPointer)( entry != NULL ? entry->pw_name : "" );
+ props[ 1 ].name = const_cast< char* >( SmUserID );
+ props[ 1 ].type = const_cast< char* >( SmARRAY8 );
+ props[ 1 ].num_vals = 1;
+ props[ 1 ].vals = &propvalue[ 1 ];
+ propvalue[ 2 ].length = 0;
+ propvalue[ 2 ].value = (SmPointer)( "" );
+ props[ 2 ].name = const_cast< char* >( SmRestartCommand );
+ props[ 2 ].type = const_cast< char* >( SmLISTofARRAY8 );
+ props[ 2 ].num_vals = 1;
+ props[ 2 ].vals = &propvalue[ 2 ];
+ propvalue[ 3 ].length = 0;
+ propvalue[ 3 ].value = qApp->argv()[ 0 ];
+ props[ 3 ].name = const_cast< char* >( SmProgram );
+ props[ 3 ].type = const_cast< char* >( SmARRAY8 );
+ props[ 3 ].num_vals = 1;
+ props[ 3 ].vals = &propvalue[ 3 ];
+ propvalue[ 4 ].length = 0;
+ propvalue[ 4 ].value = (SmPointer)( "" );
+ props[ 4 ].name = const_cast< char* >( SmCloneCommand );
+ props[ 4 ].type = const_cast< char* >( SmLISTofARRAY8 );
+ props[ 4 ].num_vals = 1;
+ props[ 4 ].vals = &propvalue[ 4 ];
+ SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] };
+ SmcSetProperties( conn, 5, p );
+ notifier = new QSocketNotifier( IceConnectionNumber( SmcGetIceConnection( conn )),
+ QSocketNotifier::Read, this );
+ connect( notifier, SIGNAL( activated( int )), SLOT( processData()));
+ }
+
+SessionSaveDoneHelper::~SessionSaveDoneHelper()
+ {
+ close();
+ }
+
+void SessionSaveDoneHelper::close()
+ {
+ if( conn != NULL )
+ {
+ delete notifier;
+ SmcCloseConnection( conn, 0, NULL );
+ }
+ conn = NULL;
+ }
+
+void SessionSaveDoneHelper::processData()
+ {
+ if( conn != NULL )
+ IceProcessMessages( SmcGetIceConnection( conn ), 0, 0 );
+ }
+
+} // namespace
+
+#include "sm.moc"