1
/*****************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
8
You can Freely distribute this program under the GNU General Public
9
License. See the file "COPYING" for the exact licensing terms.
10
******************************************************************/
14
#include <qsocketnotifier.h>
15
#include <qsessionmanager.h>
24
#include "workspace.h"
27
namespace KWinInternal
30
bool SessionManaged::saveState( QSessionManager& sm )
32
// If the session manager is ksmserver, save stacking
33
// order, active window, active desktop etc. in phase 1,
34
// as ksmserver assures no interaction will be done
35
// before the WM finishes phase 1. Saving in phase 2 is
36
// too late, as possible user interaction may change some things.
37
// Phase2 is still needed though (ICCCM 5.2)
38
char* sm_vendor = SmcVendor( static_cast< SmcConn >( sm.handle()));
39
bool ksmserver = qstrcmp( sm_vendor, "KDE" ) == 0;
43
Workspace::self()->sessionSaveStarted();
44
if( ksmserver ) // save stacking order etc. before "save file?" etc. dialogs change it
45
Workspace::self()->storeSession( kapp->sessionConfig(), SMSavePhase0 );
46
sm.release(); // Qt doesn't automatically release in this case (bug?)
50
Workspace::self()->storeSession( kapp->sessionConfig(), ksmserver ? SMSavePhase2 : SMSavePhase2Full );
51
kapp->sessionConfig()->sync();
55
// I bet this is broken, just like everywhere else in KDE
56
bool SessionManaged::commitData( QSessionManager& sm )
59
Workspace::self()->sessionSaveStarted();
66
Stores the current session in the config file
70
void Workspace::storeSession( KConfig* config, SMSavePhase phase )
72
config->setGroup("Session" );
74
int active_client = -1;
75
for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it)
78
QCString sessionId = c->sessionId();
79
QCString wmCommand = c->wmCommand();
80
if ( sessionId.isEmpty() )
81
// remember also applications that are not XSMP capable
82
// and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
83
if ( wmCommand.isEmpty() )
87
active_client = count;
88
QString n = QString::number(count);
89
if( phase == SMSavePhase2 || phase == SMSavePhase2Full )
91
config->writeEntry( QString("sessionId")+n, sessionId.data() );
92
config->writeEntry( QString("windowRole")+n, c->windowRole().data() );
93
config->writeEntry( QString("wmCommand")+n, wmCommand.data() );
94
config->writeEntry( QString("wmClientMachine")+n, c->wmClientMachine( true ).data() );
95
config->writeEntry( QString("resourceName")+n, c->resourceName().data() );
96
config->writeEntry( QString("resourceClass")+n, c->resourceClass().data() );
97
config->writeEntry( QString("geometry")+n, QRect( c->calculateGravitation(TRUE), c->clientSize() ) ); // FRAME
98
config->writeEntry( QString("restore")+n, c->geometryRestore() );
99
config->writeEntry( QString("fsrestore")+n, c->geometryFSRestore() );
100
config->writeEntry( QString("maximize")+n, (int) c->maximizeMode() );
101
config->writeEntry( QString("fullscreen")+n, (int) c->fullScreenMode() );
102
config->writeEntry( QString("desktop")+n, c->desktop() );
103
// the config entry is called "iconified" for back. comp. reasons
104
// (kconf_update script for updating session files would be too complicated)
105
config->writeEntry( QString("iconified")+n, c->isMinimized() );
106
// the config entry is called "sticky" for back. comp. reasons
107
config->writeEntry( QString("sticky")+n, c->isOnAllDesktops() );
108
config->writeEntry( QString("shaded")+n, c->isShade() );
109
// the config entry is called "staysOnTop" for back. comp. reasons
110
config->writeEntry( QString("staysOnTop")+n, c->keepAbove() );
111
config->writeEntry( QString("keepBelow")+n, c->keepBelow() );
112
config->writeEntry( QString("skipTaskbar")+n, c->skipTaskbar( true ) );
113
config->writeEntry( QString("skipPager")+n, c->skipPager() );
114
config->writeEntry( QString("userNoBorder")+n, c->isUserNoBorder() );
115
config->writeEntry( QString("windowType")+n, windowTypeToTxt( c->windowType()));
116
config->writeEntry( QString("shortcut")+n, c->shortcut().toStringInternal());
119
// TODO store also stacking order
120
if( phase == SMSavePhase0 )
122
// it would be much simpler to save these values to the config file,
123
// but both Qt and KDE treat phase1 and phase2 separately,
124
// which results in different sessionkey and different config file :(
125
session_active_client = active_client;
126
session_desktop = currentDesktop();
128
else if( phase == SMSavePhase2 )
130
config->writeEntry( "count", count );
131
config->writeEntry( "active", session_active_client );
132
config->writeEntry( "desktop", session_desktop );
134
else // SMSavePhase2Full
136
config->writeEntry( "count", count );
137
config->writeEntry( "active", session_active_client );
138
config->writeEntry( "desktop", currentDesktop());
144
Loads the session information from the config file.
148
void Workspace::loadSessionInfo()
151
KConfig* config = kapp->sessionConfig();
152
config->setGroup("Session" );
153
int count = config->readNumEntry( "count" );
154
int active_client = config->readNumEntry( "active" );
155
for ( int i = 1; i <= count; i++ )
157
QString n = QString::number(i);
158
SessionInfo* info = new SessionInfo;
159
session.append( info );
160
info->sessionId = config->readEntry( QString("sessionId")+n ).latin1();
161
info->windowRole = config->readEntry( QString("windowRole")+n ).latin1();
162
info->wmCommand = config->readEntry( QString("wmCommand")+n ).latin1();
163
info->wmClientMachine = config->readEntry( QString("wmClientMachine")+n ).latin1();
164
info->resourceName = config->readEntry( QString("resourceName")+n ).latin1();
165
info->resourceClass = config->readEntry( QString("resourceClass")+n ).lower().latin1();
166
info->geometry = config->readRectEntry( QString("geometry")+n );
167
info->restore = config->readRectEntry( QString("restore")+n );
168
info->fsrestore = config->readRectEntry( QString("fsrestore")+n );
169
info->maximized = config->readNumEntry( QString("maximize")+n, 0 );
170
info->fullscreen = config->readNumEntry( QString("fullscreen")+n, 0 );
171
info->desktop = config->readNumEntry( QString("desktop")+n, 0 );
172
info->minimized = config->readBoolEntry( QString("iconified")+n, FALSE );
173
info->onAllDesktops = config->readBoolEntry( QString("sticky")+n, FALSE );
174
info->shaded = config->readBoolEntry( QString("shaded")+n, FALSE );
175
info->keepAbove = config->readBoolEntry( QString("staysOnTop")+n, FALSE );
176
info->keepBelow = config->readBoolEntry( QString("keepBelow")+n, FALSE );
177
info->skipTaskbar = config->readBoolEntry( QString("skipTaskbar")+n, FALSE );
178
info->skipPager = config->readBoolEntry( QString("skipPager")+n, FALSE );
179
info->userNoBorder = config->readBoolEntry( QString("userNoBorder")+n, FALSE );
180
info->windowType = txtToWindowType( config->readEntry( QString("windowType")+n ).latin1());
181
info->shortcut = config->readEntry( QString("shortcut")+n );
182
info->active = ( active_client == i );
187
Returns a SessionInfo for client \a c. The returned session
188
info is removed from the storage. It's up to the caller to delete it.
190
This function is called when a new window is mapped and must be managed.
191
We try to find a matching entry in the session.
193
May return 0 if there's no session info for the client.
195
SessionInfo* Workspace::takeSessionInfo( Client* c )
197
SessionInfo *realInfo = 0;
198
QCString sessionId = c->sessionId();
199
QCString windowRole = c->windowRole();
200
QCString wmCommand = c->wmCommand();
201
QCString wmClientMachine = c->wmClientMachine( true );
202
QCString resourceName = c->resourceName();
203
QCString resourceClass = c->resourceClass();
205
// First search ``session''
206
if (! sessionId.isEmpty() )
208
// look for a real session managed client (algorithm suggested by ICCCM)
209
for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() )
210
if ( info->sessionId == sessionId && sessionInfoWindowTypeMatch( c, info ))
212
if ( ! windowRole.isEmpty() )
214
if ( info->windowRole == windowRole )
215
realInfo = session.take();
219
if ( info->windowRole.isEmpty() &&
220
info->resourceName == resourceName &&
221
info->resourceClass == resourceClass )
222
realInfo = session.take();
228
// look for a sessioninfo with matching features.
229
for (SessionInfo* info = session.first(); info && !realInfo; info = session.next() )
230
if ( info->resourceName == resourceName &&
231
info->resourceClass == resourceClass &&
232
info->wmClientMachine == wmClientMachine &&
233
sessionInfoWindowTypeMatch( c, info ))
234
if ( wmCommand.isEmpty() || info->wmCommand == wmCommand )
235
realInfo = session.take();
241
bool Workspace::sessionInfoWindowTypeMatch( Client* c, SessionInfo* info )
243
if( info->windowType == -2 )
244
{ // undefined (not really part of NET::WindowType)
245
return !c->isSpecialWindow();
247
return info->windowType == c->windowType();
250
// maybe needed later
252
// KMainWindow's without name() given have WM_WINDOW_ROLE in the form
253
// of <appname>-mainwindow#<number>
254
// when comparing them for fake session info, it's probably better to check
255
// them without the trailing number
256
bool Workspace::windowRoleMatch( const QCString& role1, const QCString& role2 )
258
if( role1.isEmpty() && role2.isEmpty())
260
int pos1 = role1.find( '#' );
261
int pos2 = role2.find( '#' );
263
if( pos1 < 0 || pos2 < 0 || pos1 != pos2 )
264
ret = role1 == role2;
266
ret = qstrncmp( role1, role2, pos1 ) == 0;
267
kdDebug() << "WR:" << role1 << ":" << pos1 << ":" << role2 << ":" << pos2 << ":::" << ret << endl;
272
static const char* const window_type_names[] =
274
"Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
275
"Override", "TopMenu", "Utility", "Splash"
277
// change also the two functions below when adding new entries
279
const char* Workspace::windowTypeToTxt( NET::WindowType type )
281
if( type >= NET::Unknown && type <= NET::Splash )
282
return window_type_names[ type + 1 ]; // +1 (unknown==-1)
283
if( type == -2 ) // undefined (not really part of NET::WindowType)
285
kdFatal() << "Unknown Window Type" << endl;
289
NET::WindowType Workspace::txtToWindowType( const char* txt )
291
for( int i = NET::Unknown;
294
if( qstrcmp( txt, window_type_names[ i + 1 ] ) == 0 ) // +1
295
return static_cast< NET::WindowType >( i );
296
return static_cast< NET::WindowType >( -2 ); // undefined
302
// KWin's focus stealing prevention causes problems with user interaction
303
// during session save, as it prevents possible dialogs from getting focus.
304
// Therefore it's temporarily disabled during session saving. Start of
305
// session saving can be detected in SessionManaged::saveState() above,
306
// but Qt doesn't have API for saying when session saved finished (either
307
// successfully, or was cancelled). Therefore, create another connection
308
// to session manager, that will provide this information.
309
// Similarly the remember feature of window-specific settings should be disabled
310
// during KDE shutdown when windows may move e.g. because of Kicker going away
311
// (struts changing). When session saving starts, it can be cancelled, in which
312
// case the shutdown_cancelled callback is invoked, or it's a checkpoint that
313
// is immediatelly followed by save_complete, or finally it's a shutdown that
314
// is immediatelly followed by die callback. So getting save_yourself with shutdown
315
// set disables window-specific settings remembering, getting shutdown_cancelled
316
// re-enables, otherwise KWin will go away after die.
317
static void save_yourself( SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool )
319
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
320
if( conn_P != session->connection())
323
Workspace::self()->disableRulesUpdates( true );
324
SmcSaveYourselfDone( conn_P, True );
327
static void die( SmcConn conn_P, SmPointer ptr )
329
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
330
if( conn_P != session->connection())
332
// session->saveDone(); we will quit anyway
336
static void save_complete( SmcConn conn_P, SmPointer ptr )
338
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
339
if( conn_P != session->connection())
344
static void shutdown_cancelled( SmcConn conn_P, SmPointer ptr )
346
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >( ptr );
347
if( conn_P != session->connection())
349
Workspace::self()->disableRulesUpdates( false ); // re-enable
350
// no need to differentiate between successful finish and cancel
354
void SessionSaveDoneHelper::saveDone()
356
Workspace::self()->sessionSaveDone();
359
SessionSaveDoneHelper::SessionSaveDoneHelper()
362
calls.save_yourself.callback = save_yourself;
363
calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this);
364
calls.die.callback = die;
365
calls.die.client_data = reinterpret_cast< SmPointer >(this);
366
calls.save_complete.callback = save_complete;
367
calls.save_complete.client_data = reinterpret_cast< SmPointer >(this);
368
calls.shutdown_cancelled.callback = shutdown_cancelled;
369
calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this);
372
conn = SmcOpenConnection( NULL, 0, 1, 0,
373
SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask
374
| SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err );
379
// set the required properties, mostly dummy values
380
SmPropValue propvalue[ 5 ];
382
propvalue[ 0 ].length = sizeof( int );
383
int value0 = SmRestartNever; // so that this extra SM connection doesn't interfere
384
propvalue[ 0 ].value = &value0;
385
props[ 0 ].name = const_cast< char* >( SmRestartStyleHint );
386
props[ 0 ].type = const_cast< char* >( SmCARD8 );
387
props[ 0 ].num_vals = 1;
388
props[ 0 ].vals = &propvalue[ 0 ];
389
struct passwd* entry = getpwuid( geteuid() );
390
propvalue[ 1 ].length = entry != NULL ? strlen( entry->pw_name ) : 0;
391
propvalue[ 1 ].value = (SmPointer)( entry != NULL ? entry->pw_name : "" );
392
props[ 1 ].name = const_cast< char* >( SmUserID );
393
props[ 1 ].type = const_cast< char* >( SmARRAY8 );
394
props[ 1 ].num_vals = 1;
395
props[ 1 ].vals = &propvalue[ 1 ];
396
propvalue[ 2 ].length = 0;
397
propvalue[ 2 ].value = (SmPointer)( "" );
398
props[ 2 ].name = const_cast< char* >( SmRestartCommand );
399
props[ 2 ].type = const_cast< char* >( SmLISTofARRAY8 );
400
props[ 2 ].num_vals = 1;
401
props[ 2 ].vals = &propvalue[ 2 ];
402
propvalue[ 3 ].length = 0;
403
propvalue[ 3 ].value = qApp->argv()[ 0 ];
404
props[ 3 ].name = const_cast< char* >( SmProgram );
405
props[ 3 ].type = const_cast< char* >( SmARRAY8 );
406
props[ 3 ].num_vals = 1;
407
props[ 3 ].vals = &propvalue[ 3 ];
408
propvalue[ 4 ].length = 0;
409
propvalue[ 4 ].value = (SmPointer)( "" );
410
props[ 4 ].name = const_cast< char* >( SmCloneCommand );
411
props[ 4 ].type = const_cast< char* >( SmLISTofARRAY8 );
412
props[ 4 ].num_vals = 1;
413
props[ 4 ].vals = &propvalue[ 4 ];
414
SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] };
415
SmcSetProperties( conn, 5, p );
416
notifier = new QSocketNotifier( IceConnectionNumber( SmcGetIceConnection( conn )),
417
QSocketNotifier::Read, this );
418
connect( notifier, SIGNAL( activated( int )), SLOT( processData()));
421
SessionSaveDoneHelper::~SessionSaveDoneHelper()
426
void SessionSaveDoneHelper::close()
431
SmcCloseConnection( conn, 0, NULL );
436
void SessionSaveDoneHelper::processData()
439
IceProcessMessages( SmcGetIceConnection( conn ), 0, 0 );