1 /*****************************************************************
2 ksmserver - the KDE session management server
4 Copyright 2000 Matthias Ettrich <ettrich@kde.org>
5 Copyright 2005 Lubos Lunak <l.lunak@kde.org>
7 relatively small extensions by Oswald Buddenhagen <ob6@inf.tu-dresden.de>
9 some code taken from the dcopserver (part of the KDE libraries), which is
10 Copyright 1999 Matthias Ettrich <ettrich@kde.org>
11 Copyright 1999 Preston Brown <pbrown@kde.org>
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
20 The above copyright notice and this permission notice shall be included in
21 all copies or substantial portions of the Software.
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
27 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
28 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 ******************************************************************/
32 #include <config-workspace.h>
34 #ifdef HAVE_SYS_TIME_H
44 #include <kconfiggroup.h>
47 #include <kwindowsystem.h>
50 #include <X11/Xutil.h>
51 #include <X11/Xatom.h>
54 * Legacy session management
57 #ifndef NO_LEGACY_SESSION_MANAGEMENT
58 const int WM_SAVE_YOURSELF_TIMEOUT
= 4000;
60 static WindowMap
* windowMapPtr
= 0;
62 static Atom wm_save_yourself
= XNone
;
63 static Atom wm_protocols
= XNone
;
64 static Atom wm_client_leader
= XNone
;
65 static Atom sm_client_id
= XNone
;
67 static int winsErrorHandler(Display
*, XErrorEvent
*ev
)
70 WindowMap::Iterator it
= windowMapPtr
->find(ev
->resourceid
);
71 if (it
!= windowMapPtr
->end())
72 (*it
).type
= SM_ERROR
;
77 void KSMServer::performLegacySessionSave()
79 kDebug( 1218 ) << "Saving legacy session apps";
80 // Setup error handler
81 legacyWindows
.clear();
82 windowMapPtr
= &legacyWindows
;
83 XErrorHandler oldHandler
= XSetErrorHandler(winsErrorHandler
);
84 // Compute set of leader windows that need legacy session management
85 // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF)
86 if( wm_save_yourself
== (Atom
)XNone
) {
88 const char* const names
[]
89 = { "WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID" };
90 XInternAtoms( QX11Info::display(), const_cast< char** >( names
), 4,
92 wm_save_yourself
= atoms
[ 0 ];
93 wm_protocols
= atoms
[ 1 ];
94 wm_client_leader
= atoms
[ 2 ];
95 sm_client_id
= atoms
[ 3 ];
97 for ( QList
<WId
>::ConstIterator it
= KWindowSystem::windows().begin();
98 it
!= KWindowSystem::windows().end(); ++it
) {
99 WId leader
= windowWmClientLeader( *it
);
100 if (!legacyWindows
.contains(leader
) && windowSessionId( *it
, leader
).isEmpty()) {
101 SMType wtype
= SM_WMCOMMAND
;
104 if( XGetWMProtocols(QX11Info::display(), leader
, &protocols
, &nprotocols
)) {
105 for (int i
=0; i
<nprotocols
; i
++)
106 if (protocols
[i
] == wm_save_yourself
) {
107 wtype
= SM_WMSAVEYOURSELF
;
110 XFree((void*) protocols
);
114 XClassHint classHint
;
115 if( XGetClassHint( QX11Info::display(), leader
, &classHint
) ) {
116 data
.wmclass1
= classHint
.res_name
;
117 data
.wmclass2
= classHint
.res_class
;
118 XFree( classHint
.res_name
);
119 XFree( classHint
.res_class
);
121 legacyWindows
.insert(leader
, data
);
124 // Open fresh display for sending WM_SAVE_YOURSELF
125 XSync(QX11Info::display(), False
);
126 Display
*newdisplay
= XOpenDisplay(DisplayString(QX11Info::display()));
129 XSetErrorHandler(oldHandler
);
132 WId root
= DefaultRootWindow(newdisplay
);
133 XGrabKeyboard(newdisplay
, root
, False
,
134 GrabModeAsync
, GrabModeAsync
, CurrentTime
);
135 XGrabPointer(newdisplay
, root
, False
, Button1Mask
|Button2Mask
|Button3Mask
,
136 GrabModeAsync
, GrabModeAsync
, XNone
, XNone
, CurrentTime
);
137 // Send WM_SAVE_YOURSELF messages
139 int awaiting_replies
= 0;
140 for (WindowMap::Iterator it
= legacyWindows
.begin(); it
!= legacyWindows
.end(); ++it
) {
141 if ( (*it
).type
== SM_WMSAVEYOURSELF
) {
143 awaiting_replies
+= 1;
144 memset(&ev
, 0, sizeof(ev
));
145 ev
.xclient
.type
= ClientMessage
;
146 ev
.xclient
.window
= w
;
147 ev
.xclient
.message_type
= wm_protocols
;
148 ev
.xclient
.format
= 32;
149 ev
.xclient
.data
.l
[0] = wm_save_yourself
;
150 ev
.xclient
.data
.l
[1] = QX11Info::appTime();
151 XSelectInput(newdisplay
, w
, PropertyChangeMask
|StructureNotifyMask
);
152 XSendEvent(newdisplay
, w
, False
, 0, &ev
);
155 // Wait for change in WM_COMMAND with timeout
157 QTime start
= QTime::currentTime();
158 while (awaiting_replies
> 0) {
159 if (XPending(newdisplay
)) {
160 /* Process pending event */
161 XNextEvent(newdisplay
, &ev
);
162 if ( ( ev
.xany
.type
== UnmapNotify
) ||
163 ( ev
.xany
.type
== PropertyNotify
&& ev
.xproperty
.atom
== XA_WM_COMMAND
) ) {
164 WindowMap::Iterator it
= legacyWindows
.find( ev
.xany
.window
);
165 if ( it
!= legacyWindows
.end() && (*it
).type
!= SM_WMCOMMAND
) {
166 awaiting_replies
-= 1;
167 if ( (*it
).type
!= SM_ERROR
)
168 (*it
).type
= SM_WMCOMMAND
;
173 int msecs
= start
.elapsed();
174 if (msecs
>= WM_SAVE_YOURSELF_TIMEOUT
)
176 /* Wait for more events */
179 int fd
= ConnectionNumber(newdisplay
);
181 struct timeval tmwait
;
182 tmwait
.tv_sec
= (WM_SAVE_YOURSELF_TIMEOUT
- msecs
) / 1000;
183 tmwait
.tv_usec
= ((WM_SAVE_YOURSELF_TIMEOUT
- msecs
) % 1000) * 1000;
184 ::select(fd
+1, &fds
, NULL
, &fds
, &tmwait
);
187 // Terminate work in new display
188 XAllowEvents(newdisplay
, ReplayPointer
, CurrentTime
);
189 XAllowEvents(newdisplay
, ReplayKeyboard
, CurrentTime
);
190 XSync(newdisplay
, False
);
191 XCloseDisplay(newdisplay
);
192 // Restore old error handler
193 XSync(QX11Info::display(), False
);
194 XSetErrorHandler(oldHandler
);
195 for (WindowMap::Iterator it
= legacyWindows
.begin(); it
!= legacyWindows
.end(); ++it
) {
196 if ( (*it
).type
!= SM_ERROR
) {
198 (*it
).wmCommand
= windowWmCommand(w
);
199 (*it
).wmClientMachine
= windowWmClientMachine(w
);
202 kDebug( 1218 ) << "Done saving " << legacyWindows
.count() << " legacy session apps";
206 Stores legacy session management data
208 void KSMServer::storeLegacySession( KConfig
* config
)
210 // Write LegacySession data
211 config
->deleteGroup( "Legacy" + sessionGroup
);
212 KConfigGroup
group( config
, "Legacy" + sessionGroup
);
214 for (WindowMap::ConstIterator it
= legacyWindows
.constBegin(); it
!= legacyWindows
.constEnd(); ++it
) {
215 if ( (*it
).type
!= SM_ERROR
) {
216 if( excludeApps
.contains( (*it
).wmclass1
.toLower())
217 || excludeApps
.contains( (*it
).wmclass2
.toLower()))
219 if ( !(*it
).wmCommand
.isEmpty() && !(*it
).wmClientMachine
.isEmpty() ) {
221 QString n
= QString::number(count
);
222 group
.writeEntry( QString("command")+n
, (*it
).wmCommand
);
223 group
.writeEntry( QString("clientMachine")+n
, (*it
).wmClientMachine
);
227 group
.writeEntry( "count", count
);
231 Restores legacy session management data (i.e. restart applications)
233 void KSMServer::restoreLegacySession( KConfig
* config
)
235 if( config
->hasGroup( "Legacy" + sessionGroup
)) {
236 KConfigGroup
group( config
, "Legacy" + sessionGroup
);
237 restoreLegacySessionInternal( &group
);
238 } else if( wm
== "kwin" ) { // backwards comp. - get it from kwinrc
239 KConfigGroup
group( config
, sessionGroup
);
240 int count
= group
.readEntry( "count", 0 );
241 for ( int i
= 1; i
<= count
; i
++ ) {
242 QString n
= QString::number(i
);
243 if ( group
.readEntry( QString("program")+n
, QString() ) != wm
)
245 QStringList restartCommand
=
246 group
.readEntry( QString("restartCommand")+n
, QStringList() );
247 for( QStringList::ConstIterator it
= restartCommand
.constBegin();
248 it
!= restartCommand
.constEnd();
250 if( (*it
) == "-session" ) {
252 if( it
!= restartCommand
.constEnd()) {
253 KConfig
cfg( "session/" + wm
+ '_' + (*it
) );
254 KConfigGroup
group(&cfg
, "LegacySession");
255 restoreLegacySessionInternal( &group
, ' ' );
263 void KSMServer::restoreLegacySessionInternal( KConfigGroup
* config
, char sep
)
265 int count
= config
->readEntry( "count",0 );
266 for ( int i
= 1; i
<= count
; i
++ ) {
267 QString n
= QString::number(i
);
268 QStringList wmCommand
= (sep
== ',') ?
269 config
->readEntry( QString("command")+n
, QStringList() ) :
270 KShell::splitArgs( config
->readEntry( QString("command")+n
, QString() ) ); // close enough(?)
271 if( wmCommand
.isEmpty())
273 if( isWM( wmCommand
.first()))
275 startApplication( wmCommand
,
276 config
->readEntry( QString("clientMachine")+n
, QString() ),
277 config
->readEntry( QString("userId")+n
, QString() ));
281 static QByteArray
getQCStringProperty(WId w
, Atom prop
)
285 unsigned long nitems
= 0;
286 unsigned long extra
= 0;
287 unsigned char *data
= 0;
288 QByteArray result
= "";
289 status
= XGetWindowProperty( QX11Info::display(), w
, prop
, 0, 10000,
290 false, XA_STRING
, &type
, &format
,
291 &nitems
, &extra
, &data
);
292 if ( status
== Success
) {
294 result
= (char*)data
;
300 static QStringList
getQStringListProperty(WId w
, Atom prop
)
304 unsigned long nitems
= 0;
305 unsigned long extra
= 0;
306 unsigned char *data
= 0;
309 status
= XGetWindowProperty( QX11Info::display(), w
, prop
, 0, 10000,
310 false, XA_STRING
, &type
, &format
,
311 &nitems
, &extra
, &data
);
312 if ( status
== Success
) {
315 for (int i
=0; i
<(int)nitems
; i
++) {
316 result
<< QLatin1String( (const char*)data
+ i
);
324 QStringList
KSMServer::windowWmCommand(WId w
)
326 QStringList ret
= getQStringListProperty(w
, XA_WM_COMMAND
);
328 if( ret
.count() == 1 ) {
329 QString command
= ret
.first();
330 // Mozilla is launched using wrapper scripts, so it's launched using "mozilla",
331 // but the actual binary is "mozilla-bin" or "<path>/mozilla-bin", and that's what
332 // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though
333 if( command
.endsWith( "mozilla-bin" ))
334 return QStringList() << "mozilla";
335 if( command
.endsWith( "firefox-bin" ))
336 return QStringList() << "firefox";
337 if( command
.endsWith( "thunderbird-bin" ))
338 return QStringList() << "thunderbird";
339 if( command
.endsWith( "sunbird-bin" ))
340 return QStringList() << "sunbird";
345 QString
KSMServer::windowWmClientMachine(WId w
)
347 QByteArray result
= getQCStringProperty(w
, XA_WM_CLIENT_MACHINE
);
348 if (result
.isEmpty()) {
349 result
= "localhost";
351 // special name for the local machine (localhost)
352 char hostnamebuf
[80];
353 if (gethostname (hostnamebuf
, sizeof hostnamebuf
) >= 0) {
354 hostnamebuf
[sizeof(hostnamebuf
)-1] = 0;
355 if (result
== hostnamebuf
)
356 result
= "localhost";
357 if(char *dot
= strchr(hostnamebuf
, '.')) {
359 if(result
== hostnamebuf
)
360 result
= "localhost";
364 return QLatin1String(result
);
367 WId
KSMServer::windowWmClientLeader(WId w
)
371 unsigned long nitems
= 0;
372 unsigned long extra
= 0;
373 unsigned char *data
= 0;
375 status
= XGetWindowProperty( QX11Info::display(), w
, wm_client_leader
, 0, 10000,
376 false, XA_WINDOW
, &type
, &format
,
377 &nitems
, &extra
, &data
);
378 if (status
== Success
) {
379 if (data
&& nitems
> 0)
380 result
= *((Window
*) data
);
388 Returns sessionId for this client,
389 taken either from its window or from the leader window.
391 QByteArray
KSMServer::windowSessionId(WId w
, WId leader
)
393 QByteArray result
= getQCStringProperty(w
, sm_client_id
);
394 if (result
.isEmpty() && leader
!= (WId
)None
&& leader
!= w
)
395 result
= getQCStringProperty(leader
, sm_client_id
);