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
if (state == ClosingSubSession)
81
return; //FIXME implement later
82
// Setup error handler
83
legacyWindows.clear();
84
windowMapPtr = &legacyWindows;
85
XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler);
86
// Compute set of leader windows that need legacy session management
87
// and determine which style (WM_COMMAND or WM_SAVE_YOURSELF)
88
if( wm_save_yourself == (Atom)XNone ) {
90
const char* const names[]
91
= { "WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID" };
92
XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 4,
94
wm_save_yourself = atoms[ 0 ];
95
wm_protocols = atoms[ 1 ];
96
wm_client_leader = atoms[ 2 ];
97
sm_client_id = atoms[ 3 ];
99
for ( QList<WId>::ConstIterator it = KWindowSystem::windows().begin();
100
it != KWindowSystem::windows().end(); ++it) {
101
WId leader = windowWmClientLeader( *it );
102
if (!legacyWindows.contains(leader) && windowSessionId( *it, leader ).isEmpty()) {
103
SMType wtype = SM_WMCOMMAND;
106
if( XGetWMProtocols(QX11Info::display(), leader, &protocols, &nprotocols)) {
107
for (int i=0; i<nprotocols; i++)
108
if (protocols[i] == wm_save_yourself) {
109
wtype = SM_WMSAVEYOURSELF;
112
XFree((void*) protocols);
116
XClassHint classHint;
117
if( XGetClassHint( QX11Info::display(), leader, &classHint ) ) {
118
data.wmclass1 = classHint.res_name;
119
data.wmclass2 = classHint.res_class;
120
XFree( classHint.res_name );
121
XFree( classHint.res_class );
123
legacyWindows.insert(leader, data);
126
// Open fresh display for sending WM_SAVE_YOURSELF
127
XSync(QX11Info::display(), False);
128
Display *newdisplay = XOpenDisplay(DisplayString(QX11Info::display()));
131
XSetErrorHandler(oldHandler);
134
WId root = DefaultRootWindow(newdisplay);
135
XGrabKeyboard(newdisplay, root, False,
136
GrabModeAsync, GrabModeAsync, CurrentTime);
137
XGrabPointer(newdisplay, root, False, Button1Mask|Button2Mask|Button3Mask,
138
GrabModeAsync, GrabModeAsync, XNone, XNone, CurrentTime);
139
// Send WM_SAVE_YOURSELF messages
141
int awaiting_replies = 0;
142
for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
143
if ( (*it).type == SM_WMSAVEYOURSELF ) {
145
awaiting_replies += 1;
146
memset(&ev, 0, sizeof(ev));
147
ev.xclient.type = ClientMessage;
148
ev.xclient.window = w;
149
ev.xclient.message_type = wm_protocols;
150
ev.xclient.format = 32;
151
ev.xclient.data.l[0] = wm_save_yourself;
152
ev.xclient.data.l[1] = QX11Info::appTime();
153
XSelectInput(newdisplay, w, PropertyChangeMask|StructureNotifyMask);
154
XSendEvent(newdisplay, w, False, 0, &ev);
157
// Wait for change in WM_COMMAND with timeout
159
QTime start = QTime::currentTime();
160
while (awaiting_replies > 0) {
161
if (XPending(newdisplay)) {
162
/* Process pending event */
163
XNextEvent(newdisplay, &ev);
164
if ( ( ev.xany.type == UnmapNotify ) ||
165
( ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND ) ) {
166
WindowMap::Iterator it = legacyWindows.find( ev.xany.window );
167
if ( it != legacyWindows.end() && (*it).type != SM_WMCOMMAND ) {
168
awaiting_replies -= 1;
169
if ( (*it).type != SM_ERROR )
170
(*it).type = SM_WMCOMMAND;
175
int msecs = start.elapsed();
176
if (msecs >= WM_SAVE_YOURSELF_TIMEOUT)
178
/* Wait for more events */
181
int fd = ConnectionNumber(newdisplay);
183
struct timeval tmwait;
184
tmwait.tv_sec = (WM_SAVE_YOURSELF_TIMEOUT - msecs) / 1000;
185
tmwait.tv_usec = ((WM_SAVE_YOURSELF_TIMEOUT - msecs) % 1000) * 1000;
186
::select(fd+1, &fds, NULL, &fds, &tmwait);
189
// Terminate work in new display
190
XAllowEvents(newdisplay, ReplayPointer, CurrentTime);
191
XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime);
192
XSync(newdisplay, False);
193
XCloseDisplay(newdisplay);
194
// Restore old error handler
195
XSync(QX11Info::display(), False);
196
XSetErrorHandler(oldHandler);
197
for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
198
if ( (*it).type != SM_ERROR) {
200
(*it).wmCommand = windowWmCommand(w);
201
(*it).wmClientMachine = windowWmClientMachine(w);
204
kDebug( 1218 ) << "Done saving " << legacyWindows.count() << " legacy session apps";
208
Stores legacy session management data
210
void KSMServer::storeLegacySession( KConfig* config )
212
if (state == ClosingSubSession)
213
return; //FIXME implement later
214
// Write LegacySession data
215
config->deleteGroup( "Legacy" + sessionGroup );
216
KConfigGroup group( config, "Legacy" + sessionGroup );
218
for (WindowMap::ConstIterator it = legacyWindows.constBegin(); it != legacyWindows.constEnd(); ++it) {
219
if ( (*it).type != SM_ERROR) {
220
if( excludeApps.contains( (*it).wmclass1.toLower())
221
|| excludeApps.contains( (*it).wmclass2.toLower()))
223
if ( !(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty() ) {
225
QString n = QString::number(count);
226
group.writeEntry( QString("command")+n, (*it).wmCommand );
227
group.writeEntry( QString("clientMachine")+n, (*it).wmClientMachine );
231
group.writeEntry( "count", count );
235
Restores legacy session management data (i.e. restart applications)
237
void KSMServer::restoreLegacySession( KConfig* config )
239
if( config->hasGroup( "Legacy" + sessionGroup )) {
240
KConfigGroup group( config, "Legacy" + sessionGroup );
241
restoreLegacySessionInternal( &group );
242
} else if( wm == "kwin" ) { // backwards comp. - get it from kwinrc
243
KConfigGroup group( config, sessionGroup );
244
int count = group.readEntry( "count", 0 );
245
for ( int i = 1; i <= count; i++ ) {
246
QString n = QString::number(i);
247
if ( group.readEntry( QString("program")+n, QString() ) != wm )
249
QStringList restartCommand =
250
group.readEntry( QString("restartCommand")+n, QStringList() );
251
for( QStringList::ConstIterator it = restartCommand.constBegin();
252
it != restartCommand.constEnd();
254
if( (*it) == "-session" ) {
256
if( it != restartCommand.constEnd()) {
257
KConfig cfg( "session/" + wm + '_' + (*it) );
258
KConfigGroup group(&cfg, "LegacySession");
259
restoreLegacySessionInternal( &group, ' ' );
267
void KSMServer::restoreLegacySessionInternal( KConfigGroup* config, char sep )
269
int count = config->readEntry( "count",0 );
270
for ( int i = 1; i <= count; i++ ) {
271
QString n = QString::number(i);
272
QStringList wmCommand = (sep == ',') ?
273
config->readEntry( QString("command")+n, QStringList() ) :
274
KShell::splitArgs( config->readEntry( QString("command")+n, QString() ) ); // close enough(?)
275
if( wmCommand.isEmpty())
277
if( isWM( wmCommand.first()))
279
startApplication( wmCommand,
280
config->readEntry( QString("clientMachine")+n, QString() ),
281
config->readEntry( QString("userId")+n, QString() ));
285
static QByteArray getQCStringProperty(WId w, Atom prop)
289
unsigned long nitems = 0;
290
unsigned long extra = 0;
291
unsigned char *data = 0;
292
QByteArray result = "";
293
status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000,
294
false, XA_STRING, &type, &format,
295
&nitems, &extra, &data );
296
if ( status == Success) {
298
result = (char*)data;
304
static QStringList getQStringListProperty(WId w, Atom prop)
308
unsigned long nitems = 0;
309
unsigned long extra = 0;
310
unsigned char *data = 0;
313
status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000,
314
false, XA_STRING, &type, &format,
315
&nitems, &extra, &data );
316
if ( status == Success) {
319
for (int i=0; i<(int)nitems; i++) {
320
result << QLatin1String( (const char*)data + i );
328
QStringList KSMServer::windowWmCommand(WId w)
330
QStringList ret = getQStringListProperty(w, XA_WM_COMMAND);
332
if( ret.count() == 1 ) {
333
QString command = ret.first();
334
// Mozilla is launched using wrapper scripts, so it's launched using "mozilla",
335
// but the actual binary is "mozilla-bin" or "<path>/mozilla-bin", and that's what
336
// will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though
337
if( command.endsWith( "mozilla-bin" ))
338
return QStringList() << "mozilla";
339
if( command.endsWith( "firefox-bin" ))
340
return QStringList() << "firefox";
341
if( command.endsWith( "thunderbird-bin" ))
342
return QStringList() << "thunderbird";
343
if( command.endsWith( "sunbird-bin" ))
344
return QStringList() << "sunbird";
345
if( command.endsWith( "seamonkey-bin" ))
346
return QStringList() << "seamonkey";
351
QString KSMServer::windowWmClientMachine(WId w)
353
QByteArray result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE);
354
if (result.isEmpty()) {
355
result = "localhost";
357
// special name for the local machine (localhost)
358
char hostnamebuf[80];
359
if (gethostname (hostnamebuf, sizeof hostnamebuf) >= 0) {
360
hostnamebuf[sizeof(hostnamebuf)-1] = 0;
361
if (result == hostnamebuf)
362
result = "localhost";
363
if(char *dot = strchr(hostnamebuf, '.')) {
365
if(result == hostnamebuf)
366
result = "localhost";
370
return QLatin1String(result);
373
WId KSMServer::windowWmClientLeader(WId w)
377
unsigned long nitems = 0;
378
unsigned long extra = 0;
379
unsigned char *data = 0;
381
status = XGetWindowProperty( QX11Info::display(), w, wm_client_leader, 0, 10000,
382
false, XA_WINDOW, &type, &format,
383
&nitems, &extra, &data );
384
if (status == Success ) {
385
if (data && nitems > 0)
386
result = *((Window*) data);
394
Returns sessionId for this client,
395
taken either from its window or from the leader window.
397
QByteArray KSMServer::windowSessionId(WId w, WId leader)
399
QByteArray result = getQCStringProperty(w, sm_client_id);
400
if (result.isEmpty() && leader != (WId)None && leader != w)
401
result = getQCStringProperty(leader, sm_client_id);