1/*****************************************************************
2ksmserver - the KDE session management server
3
4Copyright 2000 Matthias Ettrich <ettrich@kde.org>
5Copyright 2005 Lubos Lunak <l.lunak@kde.org>
6
7relatively small extensions by Oswald Buddenhagen <ob6@inf.tu-dresden.de>
8
9some code taken from the dcopserver (part of the KDE libraries), which is
10Copyright 1999 Matthias Ettrich <ettrich@kde.org>
11Copyright 1999 Preston Brown <pbrown@kde.org>
12
13Permission is hereby granted, free of charge, to any person obtaining a copy
14of this software and associated documentation files (the "Software"), to deal
15in the Software without restriction, including without limitation the rights
16to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17copies of the Software, and to permit persons to whom the Software is
18furnished to do so, subject to the following conditions:
19
20The above copyright notice and this permission notice shall be included in
21all copies or substantial portions of the Software.
22
23THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
27AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
28CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
30******************************************************************/
31
32#include <config-workspace.h>
33
34#ifdef HAVE_SYS_TIME_H
35#include <sys/time.h>
36#endif
37
38#include "server.h"
39
40#include <unistd.h>
41
42
43#include <kconfig.h>
44#include <kconfiggroup.h>
45#include <kshell.h>
46#include <kdebug.h>
47#include <kwindowsystem.h>
48
49#include <X11/Xlib.h>
50#include <X11/Xutil.h>
51#include <X11/Xatom.h>
52
53/*
54* Legacy session management
55*/
56
57#ifndef NO_LEGACY_SESSION_MANAGEMENT
58static WindowMap* windowMapPtr = 0;
59
60static Atom wm_save_yourself = XNone;
61static Atom wm_protocols = XNone;
62static Atom wm_client_leader = XNone;
63static Atom sm_client_id = XNone;
64
65static int winsErrorHandler(Display *, XErrorEvent *ev)
66{
67 if (windowMapPtr) {
68 WindowMap::Iterator it = windowMapPtr->find(ev->resourceid);
69 if (it != windowMapPtr->end())
70 (*it).type = SM_ERROR;
71 }
72 return 0;
73}
74
75void KSMServer::performLegacySessionSave()
76{
77 kDebug( 1218 ) << "Saving legacy session apps";
78 if (state == ClosingSubSession)
79 return; //FIXME implement later
80
81 KSharedConfig::Ptr config = KGlobal::config();
82 config->reparseConfiguration(); // config may have changed in the KControl module
83 KConfigGroup cg( config, "General" );
84
85 int wmSaveYourselfTimeout = cg.readEntry( "legacySaveTimeoutSecs", 4 ) * 1000;
86
87 // Setup error handler
88 legacyWindows.clear();
89 windowMapPtr = &legacyWindows;
90 XErrorHandler oldHandler = XSetErrorHandler(winsErrorHandler);
91 // Compute set of leader windows that need legacy session management
92 // and determine which style (WM_COMMAND or WM_SAVE_YOURSELF)
93 if( wm_save_yourself == (Atom)XNone ) {
94 Atom atoms[ 4 ];
95 const char* const names[]
96 = { "WM_SAVE_YOURSELF", "WM_PROTOCOLS", "WM_CLIENT_LEADER", "SM_CLIENT_ID" };
97 XInternAtoms( QX11Info::display(), const_cast< char** >( names ), 4,
98 False, atoms );
99 wm_save_yourself = atoms[ 0 ];
100 wm_protocols = atoms[ 1 ];
101 wm_client_leader = atoms[ 2 ];
102 sm_client_id = atoms[ 3 ];
103 }
104 for ( QList<WId>::ConstIterator it = KWindowSystem::windows().begin();
105 it != KWindowSystem::windows().end(); ++it) {
106 WId leader = windowWmClientLeader( *it );
107 if (!legacyWindows.contains(leader) && windowSessionId( *it, leader ).isEmpty()) {
108 SMType wtype = SM_WMCOMMAND;
109 int nprotocols = 0;
110 Atom *protocols = 0;
111 if( XGetWMProtocols(QX11Info::display(), leader, &protocols, &nprotocols)) {
112 for (int i=0; i<nprotocols; i++)
113 if (protocols[i] == wm_save_yourself) {
114 wtype = SM_WMSAVEYOURSELF;
115 break;
116 }
117 XFree((void*) protocols);
118 }
119 SMData data;
120 data.type = wtype;
121 XClassHint classHint;
122 if( XGetClassHint( QX11Info::display(), leader, &classHint ) ) {
123 data.wmclass1 = classHint.res_name;
124 data.wmclass2 = classHint.res_class;
125 XFree( classHint.res_name );
126 XFree( classHint.res_class );
127 }
128 legacyWindows.insert(leader, data);
129 }
130 }
131 // Open fresh display for sending WM_SAVE_YOURSELF
132 XSync(QX11Info::display(), False);
133 Display *newdisplay = XOpenDisplay(DisplayString(QX11Info::display()));
134 if (!newdisplay) {
135 windowMapPtr = NULL;
136 XSetErrorHandler(oldHandler);
137 return;
138 }
139 WId root = DefaultRootWindow(newdisplay);
140 XGrabKeyboard(newdisplay, root, False,
141 GrabModeAsync, GrabModeAsync, CurrentTime);
142 XGrabPointer(newdisplay, root, False, Button1Mask|Button2Mask|Button3Mask,
143 GrabModeAsync, GrabModeAsync, XNone, XNone, CurrentTime);
144 // Send WM_SAVE_YOURSELF messages
145 XEvent ev;
146 int awaiting_replies = 0;
147 for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
148 if ( (*it).type == SM_WMSAVEYOURSELF ) {
149 WId w = it.key();
150 awaiting_replies += 1;
151 memset(&ev, 0, sizeof(ev));
152 ev.xclient.type = ClientMessage;
153 ev.xclient.window = w;
154 ev.xclient.message_type = wm_protocols;
155 ev.xclient.format = 32;
156 ev.xclient.data.l[0] = wm_save_yourself;
157 ev.xclient.data.l[1] = QX11Info::appTime();
158 XSelectInput(newdisplay, w, PropertyChangeMask|StructureNotifyMask);
159 XSendEvent(newdisplay, w, False, 0, &ev);
160 kDebug( 1218 ) << "sent >save yourself< to legacy app " << (*it).wmclass1 << (*it).wmclass2;
161 }
162 }
163 // Wait for change in WM_COMMAND with timeout
164 XFlush(newdisplay);
165 QTime start = QTime::currentTime();
166 while (awaiting_replies > 0) {
167 if (XPending(newdisplay)) {
168 /* Process pending event */
169 XNextEvent(newdisplay, &ev);
170 if ( ( ev.xany.type == UnmapNotify ) ||
171 ( ev.xany.type == PropertyNotify && ev.xproperty.atom == XA_WM_COMMAND ) ) {
172 WindowMap::Iterator it = legacyWindows.find( ev.xany.window );
173 if ( it != legacyWindows.end() && (*it).type != SM_WMCOMMAND ) {
174 awaiting_replies -= 1;
175 if ( (*it).type != SM_ERROR )
176 (*it).type = SM_WMCOMMAND;
177 }
178 }
179 } else {
180 /* Check timeout */
181 int msecs = start.elapsed();
182 if (msecs >= wmSaveYourselfTimeout) {
183 kDebug( 1218 ) << "legacy timeout expired";
184 break;
185 }
186 /* Wait for more events */
187 fd_set fds;
188 FD_ZERO(&fds);
189 int fd = ConnectionNumber(newdisplay);
190 FD_SET(fd, &fds);
191 struct timeval tmwait;
192 tmwait.tv_sec = (wmSaveYourselfTimeout - msecs) / 1000;
193 tmwait.tv_usec = ((wmSaveYourselfTimeout - msecs) % 1000) * 1000;
194 ::select(fd+1, &fds, NULL, &fds, &tmwait);
195 }
196 }
197 // Terminate work in new display
198 XAllowEvents(newdisplay, ReplayPointer, CurrentTime);
199 XAllowEvents(newdisplay, ReplayKeyboard, CurrentTime);
200 XSync(newdisplay, False);
201 XCloseDisplay(newdisplay);
202 // Restore old error handler
203 XSync(QX11Info::display(), False);
204 XSetErrorHandler(oldHandler);
205 for (WindowMap::Iterator it = legacyWindows.begin(); it != legacyWindows.end(); ++it) {
206 if ( (*it).type != SM_ERROR) {
207 WId w = it.key();
208 (*it).wmCommand = windowWmCommand(w);
209 (*it).wmClientMachine = windowWmClientMachine(w);
210 }
211 }
212 kDebug( 1218 ) << "Done saving " << legacyWindows.count() << " legacy session apps";
213}
214
215/*!
216Stores legacy session management data
217*/
218void KSMServer::storeLegacySession( KConfig* config )
219{
220 if (state == ClosingSubSession)
221 return; //FIXME implement later
222 // Write LegacySession data
223 config->deleteGroup( "Legacy" + sessionGroup );
224 KConfigGroup group( config, "Legacy" + sessionGroup );
225 int count = 0;
226 for (WindowMap::ConstIterator it = legacyWindows.constBegin(); it != legacyWindows.constEnd(); ++it) {
227 if ( (*it).type != SM_ERROR) {
228 if( excludeApps.contains( (*it).wmclass1.toLower())
229 || excludeApps.contains( (*it).wmclass2.toLower()))
230 continue;
231 if ( !(*it).wmCommand.isEmpty() && !(*it).wmClientMachine.isEmpty() ) {
232 count++;
233 QString n = QString::number(count);
234 group.writeEntry( QString("command")+n, (*it).wmCommand );
235 group.writeEntry( QString("clientMachine")+n, (*it).wmClientMachine );
236 }
237 }
238 }
239 group.writeEntry( "count", count );
240}
241
242/*!
243Restores legacy session management data (i.e. restart applications)
244*/
245void KSMServer::restoreLegacySession( KConfig* config )
246{
247 if( config->hasGroup( "Legacy" + sessionGroup )) {
248 KConfigGroup group( config, "Legacy" + sessionGroup );
249 restoreLegacySessionInternal( &group );
250 } else if( wm == "kwin" ) { // backwards comp. - get it from kwinrc
251 KConfigGroup group( config, sessionGroup );
252 int count = group.readEntry( "count", 0 );
253 for ( int i = 1; i <= count; i++ ) {
254 QString n = QString::number(i);
255 if ( group.readEntry( QString("program")+n, QString() ) != wm )
256 continue;
257 QStringList restartCommand =
258 group.readEntry( QString("restartCommand")+n, QStringList() );
259 for( QStringList::ConstIterator it = restartCommand.constBegin();
260 it != restartCommand.constEnd();
261 ++it ) {
262 if( (*it) == "-session" ) {
263 ++it;
264 if( it != restartCommand.constEnd()) {
265 KConfig cfg( "session/" + wm + '_' + (*it) );
266 KConfigGroup group(&cfg, "LegacySession");
267 restoreLegacySessionInternal( &group, ' ' );
268 }
269 }
270 }
271 }
272 }
273}
274
275void KSMServer::restoreLegacySessionInternal( KConfigGroup* config, char sep )
276{
277 int count = config->readEntry( "count",0 );
278 for ( int i = 1; i <= count; i++ ) {
279 QString n = QString::number(i);
280 QStringList wmCommand = (sep == ',') ?
281 config->readEntry( QString("command")+n, QStringList() ) :
282 KShell::splitArgs( config->readEntry( QString("command")+n, QString() ) ); // close enough(?)
283 if( wmCommand.isEmpty())
284 continue;
285 if( isWM( wmCommand.first()))
286 continue;
287 startApplication( wmCommand,
288 config->readEntry( QString("clientMachine")+n, QString() ),
289 config->readEntry( QString("userId")+n, QString() ));
290 }
291}
292
293static QByteArray getQCStringProperty(WId w, Atom prop)
294{
295 Atom type;
296 int format, status;
297 unsigned long nitems = 0;
298 unsigned long extra = 0;
299 unsigned char *data = 0;
300 QByteArray result = "";
301 status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000,
302 false, XA_STRING, &type, &format,
303 &nitems, &extra, &data );
304 if ( status == Success) {
305 if( data )
306 result = (char*)data;
307 XFree(data);
308 }
309 return result;
310}
311
312static QStringList getQStringListProperty(WId w, Atom prop)
313{
314 Atom type;
315 int format, status;
316 unsigned long nitems = 0;
317 unsigned long extra = 0;
318 unsigned char *data = 0;
319 QStringList result;
320
321 status = XGetWindowProperty( QX11Info::display(), w, prop, 0, 10000,
322 false, XA_STRING, &type, &format,
323 &nitems, &extra, &data );
324 if ( status == Success) {
325 if (!data)
326 return result;
327 for (int i=0; i<(int)nitems; i++) {
328 result << QLatin1String( (const char*)data + i );
329 while(data[i]) i++;
330 }
331 XFree(data);
332 }
333 return result;
334}
335
336QStringList KSMServer::windowWmCommand(WId w)
337{
338 QStringList ret = getQStringListProperty(w, XA_WM_COMMAND);
339 // hacks here
340 if( ret.count() == 1 ) {
341 QString command = ret.first();
342 // Mozilla is launched using wrapper scripts, so it's launched using "mozilla",
343 // but the actual binary is "mozilla-bin" or "<path>/mozilla-bin", and that's what
344 // will be also in WM_COMMAND - using this "mozilla-bin" doesn't work at all though
345 if( command.endsWith( "mozilla-bin" ))
346 return QStringList() << "mozilla";
347 if( command.endsWith( "firefox-bin" ))
348 return QStringList() << "firefox";
349 if( command.endsWith( "thunderbird-bin" ))
350 return QStringList() << "thunderbird";
351 if( command.endsWith( "sunbird-bin" ))
352 return QStringList() << "sunbird";
353 if( command.endsWith( "seamonkey-bin" ))
354 return QStringList() << "seamonkey";
355 }
356 return ret;
357}
358
359QString KSMServer::windowWmClientMachine(WId w)
360{
361 QByteArray result = getQCStringProperty(w, XA_WM_CLIENT_MACHINE);
362 if (result.isEmpty()) {
363 result = "localhost";
364 } else {
365 // special name for the local machine (localhost)
366 char hostnamebuf[80];
367 if (gethostname (hostnamebuf, sizeof hostnamebuf) >= 0) {
368 hostnamebuf[sizeof(hostnamebuf)-1] = 0;
369 if (result == hostnamebuf)
370 result = "localhost";
371 if(char *dot = strchr(hostnamebuf, '.')) {
372 *dot = '\0';
373 if(result == hostnamebuf)
374 result = "localhost";
375 }
376 }
377 }
378 return QLatin1String(result);
379}
380
381WId KSMServer::windowWmClientLeader(WId w)
382{
383 Atom type;
384 int format, status;
385 unsigned long nitems = 0;
386 unsigned long extra = 0;
387 unsigned char *data = 0;
388 Window result = w;
389 status = XGetWindowProperty( QX11Info::display(), w, wm_client_leader, 0, 10000,
390 false, XA_WINDOW, &type, &format,
391 &nitems, &extra, &data );
392 if (status == Success ) {
393 if (data && nitems > 0)
394 result = *((Window*) data);
395 XFree(data);
396 }
397 return result;
398}
399
400
401/*
402Returns sessionId for this client,
403taken either from its window or from the leader window.
404*/
405QByteArray KSMServer::windowSessionId(WId w, WId leader)
406{
407 QByteArray result = getQCStringProperty(w, sm_client_id);
408 if (result.isEmpty() && leader != (WId)None && leader != w)
409 result = getQCStringProperty(leader, sm_client_id);
410 return result;
411}
412#endif
413