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 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 *********************************************************************/
31 #include "workspace.h"
33 #include <QSocketNotifier>
34 #include <QSessionManager>
40 bool SessionManager::saveState( QSessionManager
& sm
)
42 // If the session manager is ksmserver, save stacking
43 // order, active window, active desktop etc. in phase 1,
44 // as ksmserver assures no interaction will be done
45 // before the WM finishes phase 1. Saving in phase 2 is
46 // too late, as possible user interaction may change some things.
47 // Phase2 is still needed though (ICCCM 5.2)
48 char* sm_vendor
= SmcVendor( static_cast< SmcConn
>( sm
.handle()));
49 bool ksmserver
= qstrcmp( sm_vendor
, "KDE" ) == 0;
53 Workspace::self()->sessionSaveStarted();
54 if( ksmserver
) // save stacking order etc. before "save file?" etc. dialogs change it
55 Workspace::self()->storeSession( kapp
->sessionConfig(), SMSavePhase0
);
56 sm
.release(); // Qt doesn't automatically release in this case (bug?)
60 Workspace::self()->storeSession( kapp
->sessionConfig(), ksmserver
? SMSavePhase2
: SMSavePhase2Full
);
61 kapp
->sessionConfig()->sync();
65 // I bet this is broken, just like everywhere else in KDE
66 bool SessionManager::commitData( QSessionManager
& sm
)
69 Workspace::self()->sessionSaveStarted();
76 Stores the current session in the config file
80 void Workspace::storeSession( KConfig
* config
, SMSavePhase phase
)
82 KConfigGroup
cg(config
, "Session");
84 int active_client
= -1;
85 for (ClientList::Iterator it
= clients
.begin(); it
!= clients
.end(); ++it
)
88 QByteArray sessionId
= c
->sessionId();
89 QByteArray wmCommand
= c
->wmCommand();
90 if ( sessionId
.isEmpty() )
91 // remember also applications that are not XSMP capable
92 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
93 if ( wmCommand
.isEmpty() )
97 active_client
= count
;
98 QString n
= QString::number(count
);
99 if( phase
== SMSavePhase2
|| phase
== SMSavePhase2Full
)
101 cg
.writeEntry( QString("sessionId")+n
, sessionId
.constData() );
102 cg
.writeEntry( QString("windowRole")+n
, c
->windowRole().constData() );
103 cg
.writeEntry( QString("wmCommand")+n
, wmCommand
.constData() );
104 cg
.writeEntry( QString("wmClientMachine")+n
, c
->wmClientMachine( true ).constData() );
105 cg
.writeEntry( QString("resourceName")+n
, c
->resourceName().constData() );
106 cg
.writeEntry( QString("resourceClass")+n
, c
->resourceClass().constData() );
107 cg
.writeEntry( QString("geometry")+n
, QRect( c
->calculateGravitation(true), c
->clientSize() ) ); // FRAME
108 cg
.writeEntry( QString("restore")+n
, c
->geometryRestore() );
109 cg
.writeEntry( QString("fsrestore")+n
, c
->geometryFSRestore() );
110 cg
.writeEntry( QString("maximize")+n
, (int) c
->maximizeMode() );
111 cg
.writeEntry( QString("fullscreen")+n
, (int) c
->fullScreenMode() );
112 cg
.writeEntry( QString("desktop")+n
, c
->desktop() );
113 // the config entry is called "iconified" for back. comp. reasons
114 // (kconf_update script for updating session files would be too complicated)
115 cg
.writeEntry( QString("iconified")+n
, c
->isMinimized() );
116 // the config entry is called "sticky" for back. comp. reasons
117 cg
.writeEntry( QString("sticky")+n
, c
->isOnAllDesktops() );
118 cg
.writeEntry( QString("shaded")+n
, c
->isShade() );
119 // the config entry is called "staysOnTop" for back. comp. reasons
120 cg
.writeEntry( QString("staysOnTop")+n
, c
->keepAbove() );
121 cg
.writeEntry( QString("keepBelow")+n
, c
->keepBelow() );
122 cg
.writeEntry( QString("skipTaskbar")+n
, c
->skipTaskbar( true ) );
123 cg
.writeEntry( QString("skipPager")+n
, c
->skipPager() );
124 // not really just set by user, but name kept for back. comp. reasons
125 cg
.writeEntry( QString("userNoBorder")+n
, c
->noBorder() );
126 cg
.writeEntry( QString("windowType")+n
, windowTypeToTxt( c
->windowType()));
127 cg
.writeEntry( QString("shortcut")+n
, c
->shortcut().toString());
128 cg
.writeEntry( QString("stackingOrder")+n
, unconstrained_stacking_order
.indexOf( c
));
131 if( phase
== SMSavePhase0
)
133 // it would be much simpler to save these values to the config file,
134 // but both Qt and KDE treat phase1 and phase2 separately,
135 // which results in different sessionkey and different config file :(
136 session_active_client
= active_client
;
137 session_desktop
= currentDesktop();
139 else if( phase
== SMSavePhase2
)
141 cg
.writeEntry( "count", count
);
142 cg
.writeEntry( "active", session_active_client
);
143 cg
.writeEntry( "desktop", session_desktop
);
145 else // SMSavePhase2Full
147 cg
.writeEntry( "count", count
);
148 cg
.writeEntry( "active", session_active_client
);
149 cg
.writeEntry( "desktop", currentDesktop());
155 Loads the session information from the config file.
159 void Workspace::loadSessionInfo()
162 KConfigGroup
cg(kapp
->sessionConfig(), "Session");
163 int count
= cg
.readEntry( "count",0 );
164 int active_client
= cg
.readEntry( "active",0 );
165 for ( int i
= 1; i
<= count
; i
++ )
167 QString n
= QString::number(i
);
168 SessionInfo
* info
= new SessionInfo
;
169 session
.append( info
);
170 info
->sessionId
= cg
.readEntry( QString("sessionId")+n
, QString() ).toLatin1();
171 info
->windowRole
= cg
.readEntry( QString("windowRole")+n
, QString() ).toLatin1();
172 info
->wmCommand
= cg
.readEntry( QString("wmCommand")+n
, QString() ).toLatin1();
173 info
->wmClientMachine
= cg
.readEntry( QString("wmClientMachine")+n
, QString() ).toLatin1();
174 info
->resourceName
= cg
.readEntry( QString("resourceName")+n
, QString() ).toLatin1();
175 info
->resourceClass
= cg
.readEntry( QString("resourceClass")+n
, QString() ).toLower().toLatin1();
176 info
->geometry
= cg
.readEntry( QString("geometry")+n
,QRect() );
177 info
->restore
= cg
.readEntry( QString("restore")+n
,QRect() );
178 info
->fsrestore
= cg
.readEntry( QString("fsrestore")+n
,QRect() );
179 info
->maximized
= cg
.readEntry( QString("maximize")+n
, 0 );
180 info
->fullscreen
= cg
.readEntry( QString("fullscreen")+n
, 0 );
181 info
->desktop
= cg
.readEntry( QString("desktop")+n
, 0 );
182 info
->minimized
= cg
.readEntry( QString("iconified")+n
, false );
183 info
->onAllDesktops
= cg
.readEntry( QString("sticky")+n
, false );
184 info
->shaded
= cg
.readEntry( QString("shaded")+n
, false );
185 info
->keepAbove
= cg
.readEntry( QString("staysOnTop")+n
, false );
186 info
->keepBelow
= cg
.readEntry( QString("keepBelow")+n
, false );
187 info
->skipTaskbar
= cg
.readEntry( QString("skipTaskbar")+n
, false );
188 info
->skipPager
= cg
.readEntry( QString("skipPager")+n
, false );
189 info
->noBorder
= cg
.readEntry( QString("userNoBorder")+n
, false );
190 info
->windowType
= txtToWindowType( cg
.readEntry( QString("windowType")+n
, QString() ).toLatin1());
191 info
->shortcut
= cg
.readEntry( QString("shortcut")+n
, QString() );
192 info
->active
= ( active_client
== i
);
193 info
->stackingOrder
= cg
.readEntry( QString("stackingOrder")+n
, -1 );
198 Returns a SessionInfo for client \a c. The returned session
199 info is removed from the storage. It's up to the caller to delete it.
201 This function is called when a new window is mapped and must be managed.
202 We try to find a matching entry in the session.
204 May return 0 if there's no session info for the client.
206 SessionInfo
* Workspace::takeSessionInfo( Client
* c
)
208 SessionInfo
*realInfo
= 0;
209 QByteArray sessionId
= c
->sessionId();
210 QByteArray windowRole
= c
->windowRole();
211 QByteArray wmCommand
= c
->wmCommand();
212 QByteArray wmClientMachine
= c
->wmClientMachine( true );
213 QByteArray resourceName
= c
->resourceName();
214 QByteArray resourceClass
= c
->resourceClass();
216 // First search ``session''
217 if (! sessionId
.isEmpty() )
219 // look for a real session managed client (algorithm suggested by ICCCM)
220 foreach( SessionInfo
* info
, session
)
224 if( info
->sessionId
== sessionId
&& sessionInfoWindowTypeMatch( c
, info
))
226 if( ! windowRole
.isEmpty() )
228 if( info
->windowRole
== windowRole
)
231 session
.removeAll(info
);
236 if( info
->windowRole
.isEmpty()
237 && info
->resourceName
== resourceName
238 && info
->resourceClass
== resourceClass
)
241 session
.removeAll(info
);
249 // look for a sessioninfo with matching features.
250 foreach( SessionInfo
* info
, session
)
254 if( info
->resourceName
== resourceName
255 && info
->resourceClass
== resourceClass
256 && info
->wmClientMachine
== wmClientMachine
257 && sessionInfoWindowTypeMatch( c
, info
))
259 if ( wmCommand
.isEmpty() || info
->wmCommand
== wmCommand
)
262 session
.removeAll( info
);
270 bool Workspace::sessionInfoWindowTypeMatch( Client
* c
, SessionInfo
* info
)
272 if( info
->windowType
== -2 )
273 { // undefined (not really part of NET::WindowType)
274 return !c
->isSpecialWindow();
276 return info
->windowType
== c
->windowType();
279 static const char* const window_type_names
[] =
281 "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
282 "Override", "TopMenu", "Utility", "Splash"
284 // change also the two functions below when adding new entries
286 const char* Workspace::windowTypeToTxt( NET::WindowType type
)
288 if( type
>= NET::Unknown
&& type
<= NET::Splash
)
289 return window_type_names
[ type
+ 1 ]; // +1 (unknown==-1)
290 if( type
== -2 ) // undefined (not really part of NET::WindowType)
292 kFatal(1212) << "Unknown Window Type" ;
296 NET::WindowType
Workspace::txtToWindowType( const char* txt
)
298 for( int i
= NET::Unknown
;
301 if( qstrcmp( txt
, window_type_names
[ i
+ 1 ] ) == 0 ) // +1
302 return static_cast< NET::WindowType
>( i
);
303 return static_cast< NET::WindowType
>( -2 ); // undefined
309 // KWin's focus stealing prevention causes problems with user interaction
310 // during session save, as it prevents possible dialogs from getting focus.
311 // Therefore it's temporarily disabled during session saving. Start of
312 // session saving can be detected in SessionManager::saveState() above,
313 // but Qt doesn't have API for saying when session saved finished (either
314 // successfully, or was canceled). Therefore, create another connection
315 // to session manager, that will provide this information.
316 // Similarly the remember feature of window-specific settings should be disabled
317 // during KDE shutdown when windows may move e.g. because of Kicker going away
318 // (struts changing). When session saving starts, it can be cancelled, in which
319 // case the shutdown_cancelled callback is invoked, or it's a checkpoint that
320 // is immediatelly followed by save_complete, or finally it's a shutdown that
321 // is immediatelly followed by die callback. So getting save_yourself with shutdown
322 // set disables window-specific settings remembering, getting shutdown_cancelled
323 // re-enables, otherwise KWin will go away after die.
324 static void save_yourself( SmcConn conn_P
, SmPointer ptr
, int, Bool shutdown
, int, Bool
)
326 SessionSaveDoneHelper
* session
= reinterpret_cast< SessionSaveDoneHelper
* >( ptr
);
327 if( conn_P
!= session
->connection())
330 Workspace::self()->disableRulesUpdates( true );
331 SmcSaveYourselfDone( conn_P
, True
);
334 static void die( SmcConn conn_P
, SmPointer ptr
)
336 SessionSaveDoneHelper
* session
= reinterpret_cast< SessionSaveDoneHelper
* >( ptr
);
337 if( conn_P
!= session
->connection())
339 // session->saveDone(); we will quit anyway
343 static void save_complete( SmcConn conn_P
, SmPointer ptr
)
345 SessionSaveDoneHelper
* session
= reinterpret_cast< SessionSaveDoneHelper
* >( ptr
);
346 if( conn_P
!= session
->connection())
351 static void shutdown_cancelled( SmcConn conn_P
, SmPointer ptr
)
353 SessionSaveDoneHelper
* session
= reinterpret_cast< SessionSaveDoneHelper
* >( ptr
);
354 if( conn_P
!= session
->connection())
356 Workspace::self()->disableRulesUpdates( false ); // re-enable
357 // no need to differentiate between successful finish and cancel
361 void SessionSaveDoneHelper::saveDone()
363 Workspace::self()->sessionSaveDone();
366 SessionSaveDoneHelper::SessionSaveDoneHelper()
369 calls
.save_yourself
.callback
= save_yourself
;
370 calls
.save_yourself
.client_data
= reinterpret_cast< SmPointer
>(this);
371 calls
.die
.callback
= die
;
372 calls
.die
.client_data
= reinterpret_cast< SmPointer
>(this);
373 calls
.save_complete
.callback
= save_complete
;
374 calls
.save_complete
.client_data
= reinterpret_cast< SmPointer
>(this);
375 calls
.shutdown_cancelled
.callback
= shutdown_cancelled
;
376 calls
.shutdown_cancelled
.client_data
= reinterpret_cast< SmPointer
>(this);
379 conn
= SmcOpenConnection( NULL
, 0, 1, 0,
380 SmcSaveYourselfProcMask
| SmcDieProcMask
| SmcSaveCompleteProcMask
381 | SmcShutdownCancelledProcMask
, &calls
, NULL
, &id
, 10, err
);
386 // set the required properties, mostly dummy values
387 SmPropValue propvalue
[ 5 ];
389 propvalue
[ 0 ].length
= sizeof( int );
390 int value0
= SmRestartNever
; // so that this extra SM connection doesn't interfere
391 propvalue
[ 0 ].value
= &value0
;
392 props
[ 0 ].name
= const_cast< char* >( SmRestartStyleHint
);
393 props
[ 0 ].type
= const_cast< char* >( SmCARD8
);
394 props
[ 0 ].num_vals
= 1;
395 props
[ 0 ].vals
= &propvalue
[ 0 ];
396 struct passwd
* entry
= getpwuid( geteuid() );
397 propvalue
[ 1 ].length
= entry
!= NULL
? strlen( entry
->pw_name
) : 0;
398 propvalue
[ 1 ].value
= (SmPointer
)( entry
!= NULL
? entry
->pw_name
: "" );
399 props
[ 1 ].name
= const_cast< char* >( SmUserID
);
400 props
[ 1 ].type
= const_cast< char* >( SmARRAY8
);
401 props
[ 1 ].num_vals
= 1;
402 props
[ 1 ].vals
= &propvalue
[ 1 ];
403 propvalue
[ 2 ].length
= 0;
404 propvalue
[ 2 ].value
= (SmPointer
)( "" );
405 props
[ 2 ].name
= const_cast< char* >( SmRestartCommand
);
406 props
[ 2 ].type
= const_cast< char* >( SmLISTofARRAY8
);
407 props
[ 2 ].num_vals
= 1;
408 props
[ 2 ].vals
= &propvalue
[ 2 ];
409 propvalue
[ 3 ].length
= strlen( "kwinsmhelper" );
410 propvalue
[ 3 ].value
= (SmPointer
)"kwinsmhelper";
411 props
[ 3 ].name
= const_cast< char* >( SmProgram
);
412 props
[ 3 ].type
= const_cast< char* >( SmARRAY8
);
413 props
[ 3 ].num_vals
= 1;
414 props
[ 3 ].vals
= &propvalue
[ 3 ];
415 propvalue
[ 4 ].length
= 0;
416 propvalue
[ 4 ].value
= (SmPointer
)( "" );
417 props
[ 4 ].name
= const_cast< char* >( SmCloneCommand
);
418 props
[ 4 ].type
= const_cast< char* >( SmLISTofARRAY8
);
419 props
[ 4 ].num_vals
= 1;
420 props
[ 4 ].vals
= &propvalue
[ 4 ];
421 SmProp
* p
[ 5 ] = { &props
[ 0 ], &props
[ 1 ], &props
[ 2 ], &props
[ 3 ], &props
[ 4 ] };
422 SmcSetProperties( conn
, 5, p
);
423 notifier
= new QSocketNotifier( IceConnectionNumber( SmcGetIceConnection( conn
)),
424 QSocketNotifier::Read
, this );
425 connect( notifier
, SIGNAL( activated( int )), SLOT( processData()));
428 SessionSaveDoneHelper::~SessionSaveDoneHelper()
433 void SessionSaveDoneHelper::close()
438 SmcCloseConnection( conn
, 0, NULL
);
443 void SessionSaveDoneHelper::processData()
446 IceProcessMessages( SmcGetIceConnection( conn
), 0, 0 );