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 <kglobalsettings.h>
33#include <QDir>
34#include <krun.h>
35#include <config-workspace.h>
36#include <config-unix.h> // HAVE_LIMITS_H
37
38#include <pwd.h>
39#include <sys/types.h>
40#include <sys/param.h>
41#include <sys/stat.h>
42#ifdef HAVE_SYS_TIME_H
43#include <sys/time.h>
44#endif
45#include <sys/socket.h>
46#include <sys/un.h>
47
48#include <unistd.h>
49#include <stdlib.h>
50#include <signal.h>
51#include <time.h>
52#include <errno.h>
53#include <string.h>
54#include <assert.h>
55
56#ifdef HAVE_LIMITS_H
57#include <limits.h>
58#endif
59
60#include <QPushButton>
61#include <QTimer>
62#include <QtDBus/QtDBus>
63
64#include <klocale.h>
65#include <kglobal.h>
66#include <kconfig.h>
67#include <kstandarddirs.h>
68#include <kapplication.h>
69#include <ktemporaryfile.h>
70#include <knotification.h>
71#include <kconfiggroup.h>
72#include <kprocess.h>
73
74#include "global.h"
75#include "server.h"
76#include "client.h"
77#include <kdebug.h>
78
79#include <QX11Info>
80
81//#include "kdesktop_interface.h"
82#include <klauncher_iface.h>
83#include "kcminit_interface.h"
84
85//#define KSMSERVER_STARTUP_DEBUG1
86
87#ifdef KSMSERVER_STARTUP_DEBUG1
88static QTime t;
89#endif
90
91/*! Restores the previous session. Ensures the window manager is
92 running (if specified).
93 */
94void KSMServer::restoreSession( const QString &sessionName )
95{
96 if( state != Idle )
97 return;
98#ifdef KSMSERVER_STARTUP_DEBUG1
99 t.start();
100#endif
101 state = LaunchingWM;
102
103 kDebug( 1218 ) << "KSMServer::restoreSession " << sessionName;
104 KSharedConfig::Ptr config = KGlobal::config();
105
106 sessionGroup = "Session: " + sessionName;
107 KConfigGroup configSessionGroup( config, sessionGroup);
108
109 int count = configSessionGroup.readEntry( "count", 0 );
110 appsToStart = count;
111 upAndRunning( "ksmserver" );
112 connect( klauncherSignals, SIGNAL(autoStart0Done()), SLOT(autoStart0Done()));
113 connect( klauncherSignals, SIGNAL(autoStart1Done()), SLOT(autoStart1Done()));
114 connect( klauncherSignals, SIGNAL(autoStart2Done()), SLOT(autoStart2Done()));
115
116 // find all commands to launch the wm in the session
117 QList<QStringList> wmStartCommands;
118 if ( !wm.isEmpty() ) {
119 for ( int i = 1; i <= count; i++ ) {
120 QString n = QString::number(i);
121 if ( wm == configSessionGroup.readEntry( QString("program")+n, QString() ) ) {
122 wmStartCommands << configSessionGroup.readEntry( QString("restartCommand")+n, QStringList() );
123 }
124 }
125 }
126 if( wmStartCommands.isEmpty()) // otherwise use the configured default
127 wmStartCommands << wmCommands;
128
129 launchWM( wmStartCommands );
130}
131
132/*!
133 Starts the default session.
134
135 Currently, that's the window manager only (if specified).
136 */
137void KSMServer::startDefaultSession()
138{
139 if( state != Idle )
140 return;
141 state = LaunchingWM;
142#ifdef KSMSERVER_STARTUP_DEBUG1
143 t.start();
144#endif
145 sessionGroup = "";
146 upAndRunning( "ksmserver" );
147 connect( klauncherSignals, SIGNAL(autoStart0Done()), SLOT(autoStart0Done()));
148 connect( klauncherSignals, SIGNAL(autoStart1Done()), SLOT(autoStart1Done()));
149 connect( klauncherSignals, SIGNAL(autoStart2Done()), SLOT(autoStart2Done()));
150
151 launchWM( QList< QStringList >() << wmCommands );
152}
153
154void KSMServer::launchWM( const QList< QStringList >& wmStartCommands )
155{
156 assert( state == LaunchingWM );
157
158 // when we have a window manager, we start it first and give
159 // it some time before launching other processes. Results in a
160 // visually more appealing startup.
161 wmProcess = startApplication( wmStartCommands[ 0 ], QString(), QString(), true );
162 connect( wmProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(wmProcessChange()));
163 connect( wmProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(wmProcessChange()));
164 QTimer::singleShot( 4000, this, SLOT(autoStart0()) );
165}
166
167void KSMServer::clientSetProgram( KSMClient* client )
168{
169 if( client->program() == wm )
170 autoStart0();
171#ifndef KSMSERVER_STARTUP_DEBUGl
172 if( state == Idle )
173 {
174 static int cnt = 0;
175 if( client->program() == "gedit" && ( cnt == 0 ))
176 ++cnt;
177 else if( client->program() == "konqueror" && ( cnt == 1 ))
178 ++cnt;
179 else if( client->program() == "kspaceduel" && ( cnt == 2 ))
180 ++cnt;
181 else if( client->program() == "gedit" && ( cnt == 3 ))
182 ++cnt;
183 else
184 cnt = 0;
185 if( cnt == 4 )
186 KMessageBox::information( NULL, "drat" );
187 }
188#endif
189}
190
191void KSMServer::wmProcessChange()
192{
193 if( state != LaunchingWM )
194 { // don't care about the process when not in the wm-launching state anymore
195 wmProcess = NULL;
196 return;
197 }
198 if( wmProcess->state() == QProcess::NotRunning )
199 { // wm failed to launch for some reason, go with kwin instead
200 kWarning( 1218 ) << "Window manager" << wm << "failed to launch";
201 if( wm == "kwin" )
202 return; // uhoh, kwin itself failed
203 kDebug( 1218 ) << "Launching KWin";
204 wm = "kwin";
205 wmCommands = ( QStringList() << "kwin" );
206 // launch it
207 launchWM( QList< QStringList >() << wmCommands );
208 return;
209 }
210}
211
212void KSMServer::autoStart0()
213{
214 if( state != LaunchingWM )
215 return;
216 if( !checkStartupSuspend())
217 return;
218 state = AutoStart0;
219#ifdef KSMSERVER_STARTUP_DEBUG1
220 kDebug() << t.elapsed();
221#endif
222 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
223 klauncher.autoStart((int)0);
224}
225
226void KSMServer::autoStart0Done()
227{
228 if( state != AutoStart0 )
229 return;
230 disconnect( klauncherSignals, SIGNAL(autoStart0Done()), this, SLOT(autoStart0Done()));
231 if( !checkStartupSuspend())
232 return;
233 kDebug( 1218 ) << "Autostart 0 done";
234#ifdef KSMSERVER_STARTUP_DEBUG1
235 kDebug() << t.elapsed();
236#endif
237 upAndRunning( "desktop" );
238 state = KcmInitPhase1;
239 kcminitSignals = new QDBusInterface("org.kde.kcminit", "/kcminit", "org.kde.KCMInit", QDBusConnection::sessionBus(), this );
240 if( !kcminitSignals->isValid()) {
241 kWarning() << "kcminit not running? If we are running with mobile profile or in another platform other than X11 this is normal.";
242 delete kcminitSignals;
243 kcminitSignals = 0;
244 QTimer::singleShot(0, this, SLOT(kcmPhase1Done()));
245 return;
246 }
247 connect( kcminitSignals, SIGNAL(phase1Done()), SLOT(kcmPhase1Done()));
248 QTimer::singleShot( 10000, this, SLOT(kcmPhase1Timeout())); // protection
249
250 org::kde::KCMInit kcminit("org.kde.kcminit", "/kcminit" , QDBusConnection::sessionBus());
251 kcminit.runPhase1();
252}
253
254void KSMServer::kcmPhase1Done()
255{
256 if( state != KcmInitPhase1 )
257 return;
258 kDebug( 1218 ) << "Kcminit phase 1 done";
259 if (kcminitSignals) {
260 disconnect( kcminitSignals, SIGNAL(phase1Done()), this, SLOT(kcmPhase1Done()));
261 }
262 autoStart1();
263}
264
265void KSMServer::kcmPhase1Timeout()
266{
267 if( state != KcmInitPhase1 )
268 return;
269 kDebug( 1218 ) << "Kcminit phase 1 timeout";
270 kcmPhase1Done();
271}
272
273void KSMServer::autoStart1()
274{
275 if( state != KcmInitPhase1 )
276 return;
277 state = AutoStart1;
278#ifdef KSMSERVER_STARTUP_DEBUG1
279 kDebug() << t.elapsed();
280#endif
281 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
282 klauncher.autoStart((int)1);
283}
284
285void KSMServer::autoStart1Done()
286{
287 if( state != AutoStart1 )
288 return;
289 disconnect( klauncherSignals, SIGNAL(autoStart1Done()), this, SLOT(autoStart1Done()));
290 if( !checkStartupSuspend())
291 return;
292 kDebug( 1218 ) << "Autostart 1 done";
293 setupShortcuts(); // done only here, because it needs kglobalaccel :-/
294 lastAppStarted = 0;
295 lastIdStarted.clear();
296 state = Restoring;
297#ifdef KSMSERVER_STARTUP_DEBUG1
298 kDebug() << t.elapsed();
299#endif
300 if( defaultSession()) {
301 autoStart2();
302 return;
303 }
304 tryRestoreNext();
305}
306
307void KSMServer::clientRegistered( const char* previousId )
308{
309 if ( previousId && lastIdStarted == previousId )
310 tryRestoreNext();
311}
312
313void KSMServer::tryRestoreNext()
314{
315 if( state != Restoring && state != RestoringSubSession )
316 return;
317 restoreTimer.stop();
318 startupSuspendTimeoutTimer.stop();
319 KConfigGroup config(KGlobal::config(), sessionGroup );
320
321 while ( lastAppStarted < appsToStart ) {
322 lastAppStarted++;
323 QString n = QString::number(lastAppStarted);
324 QString clientId = config.readEntry( QString("clientId")+n, QString() );
325 bool alreadyStarted = false;
326 foreach ( KSMClient *c, clients ) {
327 if ( c->clientId() == clientId ) {
328 alreadyStarted = true;
329 break;
330 }
331 }
332 if ( alreadyStarted )
333 continue;
334
335 QStringList restartCommand = config.readEntry( QString("restartCommand")+n, QStringList() );
336 if ( restartCommand.isEmpty() ||
337 (config.readEntry( QString("restartStyleHint")+n, 0 ) == SmRestartNever)) {
338 continue;
339 }
340 if ( wm == config.readEntry( QString("program")+n, QString() ) )
341 continue; // wm already started
342 if( config.readEntry( QString( "wasWm" )+n, false ))
343 continue; // it was wm before, but not now, don't run it (some have --replace in command :( )
344 startApplication( restartCommand,
345 config.readEntry( QString("clientMachine")+n, QString() ),
346 config.readEntry( QString("userId")+n, QString() ));
347 lastIdStarted = clientId;
348 if ( !lastIdStarted.isEmpty() ) {
349 restoreTimer.setSingleShot( true );
350 restoreTimer.start( 2000 );
351 return; // we get called again from the clientRegistered handler
352 }
353 }
354
355 //all done
356 appsToStart = 0;
357 lastIdStarted.clear();
358
359 if (state == Restoring)
360 autoStart2();
361 else { //subsession
362 state = Idle;
363 emit subSessionOpened();
364 }
365}
366
367void KSMServer::autoStart2()
368{
369 if( state != Restoring )
370 return;
371 if( !checkStartupSuspend())
372 return;
373 state = FinishingStartup;
374#ifdef KSMSERVER_STARTUP_DEBUG1
375 kDebug() << t.elapsed();
376#endif
377 waitAutoStart2 = true;
378 waitKcmInit2 = true;
379 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
380 klauncher.autoStart((int)2);
381
382#ifdef KSMSERVER_STARTUP_DEBUG1
383 kDebug() << "klauncher" << t.elapsed();
384#endif
385
386 QDBusInterface kded( "org.kde.kded", "/kded", "org.kde.kded" );
387 kded.call( "loadSecondPhase" );
388
389#ifdef KSMSERVER_STARTUP_DEBUG1
390 kDebug() << "kded" << t.elapsed();
391#endif
392
393 runUserAutostart();
394
395 if (kcminitSignals) {
396 connect( kcminitSignals, SIGNAL(phase2Done()), SLOT(kcmPhase2Done()));
397 QTimer::singleShot( 10000, this, SLOT(kcmPhase2Timeout())); // protection
398 org::kde::KCMInit kcminit("org.kde.kcminit", "/kcminit" , QDBusConnection::sessionBus());
399 kcminit.runPhase2();
400 } else {
401 QTimer::singleShot(0, this, SLOT(kcmPhase2Done()));
402 }
403 if( !defaultSession())
404 restoreLegacySession(KGlobal::config().data());
405 KNotification::event( "startkde" , QString() , QPixmap() , 0l , KNotification::DefaultEvent ); // this is the time KDE is up, more or less
406}
407
408void KSMServer::runUserAutostart()
409{
410 // now let's execute all the stuff in the autostart folder.
411 // the stuff will actually be really executed when the event loop is
412 // entered, since KRun internally uses a QTimer
413 QDir dir( KGlobalSettings::autostartPath() );
414 if (dir.exists()) {
415 const QStringList entries = dir.entryList( QDir::Files );
416 foreach (const QString& file, entries) {
417 // Don't execute backup files
418 if ( !file.endsWith('~') && !file.endsWith(".bak") &&
419 ( file[0] != '%' || !file.endsWith('%') ) &&
420 ( file[0] != '#' || !file.endsWith('#') ) )
421 {
422 KUrl url( dir.absolutePath() + '/' + file );
423 (void) new KRun( url, 0, true );
424 }
425 }
426 } else {
427 // Create dir so that users can find it :-)
428 dir.mkpath( KGlobalSettings::autostartPath() );
429 }
430}
431
432void KSMServer::autoStart2Done()
433{
434 if( state != FinishingStartup )
435 return;
436 disconnect( klauncherSignals, SIGNAL(autoStart2Done()), this, SLOT(autoStart2Done()));
437 kDebug( 1218 ) << "Autostart 2 done";
438 waitAutoStart2 = false;
439 finishStartup();
440}
441
442void KSMServer::kcmPhase2Done()
443{
444 if( state != FinishingStartup )
445 return;
446 kDebug( 1218 ) << "Kcminit phase 2 done";
447 if (kcminitSignals) {
448 disconnect( kcminitSignals, SIGNAL(phase2Done()), this, SLOT(kcmPhase2Done()));
449 delete kcminitSignals;
450 kcminitSignals = 0;
451 }
452 waitKcmInit2 = false;
453 finishStartup();
454}
455
456void KSMServer::kcmPhase2Timeout()
457{
458 if( !waitKcmInit2 )
459 return;
460 kDebug( 1218 ) << "Kcminit phase 2 timeout";
461 kcmPhase2Done();
462}
463
464void KSMServer::finishStartup()
465{
466 if( state != FinishingStartup )
467 return;
468 if( waitAutoStart2 || waitKcmInit2 )
469 return;
470
471 upAndRunning( "ready" );
472#ifdef KSMSERVER_STARTUP_DEBUG1
473 kDebug() << t.elapsed();
474#endif
475
476 state = Idle;
477 setupXIOErrorHandler(); // From now on handle X errors as normal shutdown.
478}
479
480bool KSMServer::checkStartupSuspend()
481{
482 if( startupSuspendCount.isEmpty())
483 return true;
484 // wait for the phase to finish
485 if( !startupSuspendTimeoutTimer.isActive())
486 {
487 startupSuspendTimeoutTimer.setSingleShot( true );
488 startupSuspendTimeoutTimer.start( 10000 );
489 }
490 return false;
491}
492
493void KSMServer::suspendStartup( const QString &app )
494{
495 if( !startupSuspendCount.contains( app ))
496 startupSuspendCount[ app ] = 0;
497 ++startupSuspendCount[ app ];
498}
499
500void KSMServer::resumeStartup( const QString &app )
501{
502 if( !startupSuspendCount.contains( app ))
503 return;
504 if( --startupSuspendCount[ app ] == 0 ) {
505 startupSuspendCount.remove( app );
506 if( startupSuspendCount.isEmpty() && startupSuspendTimeoutTimer.isActive()) {
507 startupSuspendTimeoutTimer.stop();
508 resumeStartupInternal();
509 }
510 }
511}
512
513void KSMServer::startupSuspendTimeout()
514{
515 kDebug( 1218 ) << "Startup suspend timeout:" << state;
516 resumeStartupInternal();
517}
518
519void KSMServer::resumeStartupInternal()
520{
521 startupSuspendCount.clear();
522 switch( state ) {
523 case LaunchingWM:
524 autoStart0();
525 break;
526 case AutoStart0:
527 autoStart0Done();
528 break;
529 case AutoStart1:
530 autoStart1Done();
531 break;
532 case Restoring:
533 autoStart2();
534 break;
535 default:
536 kWarning( 1218 ) << "Unknown resume startup state" ;
537 break;
538 }
539}
540
541void KSMServer::upAndRunning( const QString& msg )
542{
543 XEvent e;
544 e.xclient.type = ClientMessage;
545 e.xclient.message_type = XInternAtom( QX11Info::display(), "_KDE_SPLASH_PROGRESS", False );
546 e.xclient.display = QX11Info::display();
547 e.xclient.window = QX11Info::appRootWindow();
548 e.xclient.format = 8;
549 assert( strlen( msg.toLatin1()) < 20 );
550 strcpy( e.xclient.data.b, msg.toLatin1());
551 XSendEvent( QX11Info::display(), QX11Info::appRootWindow(), False, SubstructureNotifyMask, &e );
552}
553
554void KSMServer::restoreSubSession( const QString& name )
555{
556 sessionGroup = "SubSession: " + name;
557
558 KConfigGroup configSessionGroup( KGlobal::config(), sessionGroup);
559 int count = configSessionGroup.readEntry( "count", 0 );
560 appsToStart = count;
561 lastAppStarted = 0;
562 lastIdStarted.clear();
563
564 state = RestoringSubSession;
565 tryRestoreNext();
566}
567