not quite so much needs to be delayed to the init() function
[personal-kdebase.git] / workspace / ksmserver / startup.cpp
blob71dd851bc7d176ad289a0e27e8793d2b56a15e7b
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 <kglobalsettings.h>
33 #include <QDir>
34 #include <krun.h>
35 #include <config-workspace.h>
36 #include <config-unix.h> // HAVE_LIMITS_H
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>
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>
56 #ifdef HAVE_LIMITS_H
57 #include <limits.h>
58 #endif
60 #include <QPushButton>
61 #include <QTimer>
62 #include <QtDBus/QtDBus>
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>
74 #include "global.h"
75 #include "server.h"
76 #include "client.h"
77 #include <kdebug.h>
79 #include <QX11Info>
81 //#include "kdesktop_interface.h"
82 #include "klauncher_interface.h"
83 #include "kcminit_interface.h"
85 /*! Restores the previous session. Ensures the window manager is
86 running (if specified).
88 void KSMServer::restoreSession( const QString &sessionName )
90 if( state != Idle )
91 return;
92 state = LaunchingWM;
94 kDebug( 1218 ) << "KSMServer::restoreSession " << sessionName;
95 KSharedConfig::Ptr config = KGlobal::config();
97 sessionGroup = "Session: " + sessionName;
98 KConfigGroup configSessionGroup( config, sessionGroup);
100 int count = configSessionGroup.readEntry( "count", 0 );
101 appsToStart = count;
102 publishProgress( appsToStart, true );
103 upAndRunning( "ksmserver" );
104 connect( klauncherSignals, SIGNAL( autoStart0Done()), SLOT( autoStart0Done()));
105 connect( klauncherSignals, SIGNAL( autoStart1Done()), SLOT( autoStart1Done()));
106 connect( klauncherSignals, SIGNAL( autoStart2Done()), SLOT( autoStart2Done()));
108 // find all commands to launch the wm in the session
109 QList<QStringList> wmStartCommands;
110 if ( !wm.isEmpty() ) {
111 for ( int i = 1; i <= count; i++ ) {
112 QString n = QString::number(i);
113 if ( wm == configSessionGroup.readEntry( QString("program")+n, QString() ) ) {
114 wmStartCommands << configSessionGroup.readEntry( QString("restartCommand")+n, QStringList() );
118 if( wmStartCommands.isEmpty()) // otherwise use the configured default
119 wmStartCommands << wmCommands;
121 launchWM( wmStartCommands );
125 Starts the default session.
127 Currently, that's the window manager only (if specified).
129 void KSMServer::startDefaultSession()
131 if( state != Idle )
132 return;
133 state = LaunchingWM;
134 sessionGroup = "";
135 publishProgress( 0, true );
136 upAndRunning( "ksmserver" );
137 connect( klauncherSignals, SIGNAL( autoStart0Done()), SLOT( autoStart0Done()));
138 connect( klauncherSignals, SIGNAL( autoStart1Done()), SLOT( autoStart1Done()));
139 connect( klauncherSignals, SIGNAL( autoStart2Done()), SLOT( autoStart2Done()));
141 launchWM( QList< QStringList >() << wmCommands );
144 void KSMServer::launchWM( const QList< QStringList >& wmStartCommands )
146 assert( state == LaunchingWM );
148 // when we have a window manager, we start it first and give
149 // it some time before launching other processes. Results in a
150 // visually more appealing startup.
151 wmProcess = startApplication( wmStartCommands[ 0 ] );
152 connect( wmProcess, SIGNAL( error( QProcess::ProcessError )), SLOT( wmProcessChange()));
153 connect( wmProcess, SIGNAL( finished( int, QProcess::ExitStatus )), SLOT( wmProcessChange()));
154 // there can be possibly more wm's (because of forking for multihead),
155 // but in such case care only about the process of the first one
156 for (int i = 1; i < wmStartCommands.count(); i++)
157 startApplication( wmStartCommands[i] );
158 QTimer::singleShot( 4000, this, SLOT( autoStart0() ) );
161 void KSMServer::clientSetProgram( KSMClient* client )
163 if( client->program() == wm )
164 autoStart0();
167 void KSMServer::wmProcessChange()
169 if( state != LaunchingWM )
170 { // don't care about the process when not in the wm-launching state anymore
171 wmProcess = NULL;
172 return;
174 if( wmProcess->state() == QProcess::NotRunning )
175 { // wm failed to launch for some reason, go with kwin instead
176 kWarning( 1218 ) << "Window manager" << wm << "failed to launch";
177 if( wm == "kwin" )
178 return; // uhoh, kwin itself failed
179 kDebug( 1218 ) << "Launching KWin";
180 wm = "kwin";
181 wmCommands = ( QStringList() << "kwin" );
182 // launch it
183 launchWM( QList< QStringList >() << wmCommands );
184 return;
188 void KSMServer::autoStart0()
190 if( state != LaunchingWM )
191 return;
192 if( !checkStartupSuspend())
193 return;
194 state = AutoStart0;
195 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
196 klauncher.autoStart((int)0);
199 void KSMServer::autoStart0Done()
201 if( state != AutoStart0 )
202 return;
203 disconnect( klauncherSignals, SIGNAL( autoStart0Done()), this, SLOT( autoStart0Done()));
204 if( !checkStartupSuspend())
205 return;
206 kDebug( 1218 ) << "Autostart 0 done";
207 upAndRunning( "desktop" );
208 kcminitSignals = new QDBusInterface("org.kde.kcminit", "/kcminit", "org.kde.KCMInit", QDBusConnection::sessionBus(), this );
209 if( !kcminitSignals->isValid())
210 kWarning() << "kcminit not running?" ;
211 connect( kcminitSignals, SIGNAL( phase1Done()), SLOT( kcmPhase1Done()));
212 state = KcmInitPhase1;
213 QTimer::singleShot( 10000, this, SLOT( kcmPhase1Timeout())); // protection
215 org::kde::KCMInit kcminit("org.kde.kcminit", "/kcminit" , QDBusConnection::sessionBus());
216 kcminit.runPhase1();
219 void KSMServer::kcmPhase1Done()
221 if( state != KcmInitPhase1 )
222 return;
223 kDebug( 1218 ) << "Kcminit phase 1 done";
224 disconnect( kcminitSignals, SIGNAL( phase1Done()), this, SLOT( kcmPhase1Done()));
225 autoStart1();
228 void KSMServer::kcmPhase1Timeout()
230 if( state != KcmInitPhase1 )
231 return;
232 kDebug( 1218 ) << "Kcminit phase 1 timeout";
233 kcmPhase1Done();
236 void KSMServer::autoStart1()
238 if( state != KcmInitPhase1 )
239 return;
240 state = AutoStart1;
241 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
242 klauncher.autoStart((int)1);
245 void KSMServer::autoStart1Done()
247 if( state != AutoStart1 )
248 return;
249 disconnect( klauncherSignals, SIGNAL( autoStart1Done()), this, SLOT( autoStart1Done()));
250 if( !checkStartupSuspend())
251 return;
252 kDebug( 1218 ) << "Autostart 1 done";
253 lastAppStarted = 0;
254 lastIdStarted.clear();
255 state = Restoring;
256 if( defaultSession()) {
257 autoStart2();
258 return;
260 tryRestoreNext();
263 void KSMServer::clientRegistered( const char* previousId )
265 if ( previousId && lastIdStarted == previousId )
266 tryRestoreNext();
269 void KSMServer::tryRestoreNext()
271 if( state != Restoring )
272 return;
273 restoreTimer.stop();
274 startupSuspendTimeoutTimer.stop();
275 KConfigGroup config(KGlobal::config(), sessionGroup );
277 while ( lastAppStarted < appsToStart ) {
278 publishProgress ( appsToStart - lastAppStarted );
279 lastAppStarted++;
280 QString n = QString::number(lastAppStarted);
281 QStringList restartCommand = config.readEntry( QString("restartCommand")+n, QStringList() );
282 if ( restartCommand.isEmpty() ||
283 (config.readEntry( QString("restartStyleHint")+n, 0 ) == SmRestartNever)) {
284 continue;
286 if ( wm == config.readEntry( QString("program")+n, QString() ) )
287 continue; // wm already started
288 if( config.readEntry( QString( "wasWm" )+n, false ))
289 continue; // it was wm before, but not now, don't run it (some have --replace in command :( )
290 startApplication( restartCommand,
291 config.readEntry( QString("clientMachine")+n, QString() ),
292 config.readEntry( QString("userId")+n, QString() ));
293 lastIdStarted = config.readEntry( QString("clientId")+n, QString() );
294 if ( !lastIdStarted.isEmpty() ) {
295 restoreTimer.setSingleShot( true );
296 restoreTimer.start( 2000 );
297 return; // we get called again from the clientRegistered handler
301 appsToStart = 0;
302 lastIdStarted.clear();
303 publishProgress( 0 );
305 autoStart2();
308 void KSMServer::autoStart2()
310 if( state != Restoring )
311 return;
312 if( !checkStartupSuspend())
313 return;
314 state = FinishingStartup;
315 waitAutoStart2 = true;
316 waitKcmInit2 = true;
317 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
318 klauncher.autoStart((int)2);
320 QDBusInterface kded( "org.kde.kded", "/kded", "org.kde.kded" );
321 kded.call( "loadSecondPhase" );
323 runUserAutostart();
325 connect( kcminitSignals, SIGNAL( phase2Done()), SLOT( kcmPhase2Done()));
326 QTimer::singleShot( 10000, this, SLOT( kcmPhase2Timeout())); // protection
327 org::kde::KCMInit kcminit("org.kde.kcminit", "/kcminit" , QDBusConnection::sessionBus());
328 kcminit.runPhase2();
329 if( !defaultSession())
330 restoreLegacySession(KGlobal::config().data());
331 KNotification::event( "startkde" , QString() , QPixmap() , 0l , KNotification::DefaultEvent ); // this is the time KDE is up, more or less
334 void KSMServer::runUserAutostart()
336 // now let's execute all the stuff in the autostart folder.
337 // the stuff will actually be really executed when the event loop is
338 // entered, since KRun internally uses a QTimer
339 QDir dir( KGlobalSettings::autostartPath() );
340 if (dir.exists()) {
341 const QStringList entries = dir.entryList( QDir::Files );
342 foreach (const QString& file, entries) {
343 // Don't execute backup files
344 if ( !file.endsWith('~') && !file.endsWith(".bak") &&
345 ( file[0] != '%' || !file.endsWith('%') ) &&
346 ( file[0] != '#' || !file.endsWith('#') ) )
348 KUrl url( dir.absolutePath() + '/' + file );
349 (void) new KRun( url, 0, true );
352 } else {
353 // Create dir so that users can find it :-)
354 dir.mkpath( KGlobalSettings::autostartPath() );
358 void KSMServer::autoStart2Done()
360 if( state != FinishingStartup )
361 return;
362 disconnect( klauncherSignals, SIGNAL( autoStart2Done()), this, SLOT( autoStart2Done()));
363 kDebug( 1218 ) << "Autostart 2 done";
364 waitAutoStart2 = false;
365 finishStartup();
368 void KSMServer::kcmPhase2Done()
370 if( state != FinishingStartup )
371 return;
372 kDebug( 1218 ) << "Kcminit phase 2 done";
373 disconnect( kcminitSignals, SIGNAL( phase2Done()), this, SLOT( kcmPhase2Done()));
374 delete kcminitSignals;
375 kcminitSignals = NULL;
376 waitKcmInit2 = false;
377 finishStartup();
380 void KSMServer::kcmPhase2Timeout()
382 if( !waitKcmInit2 )
383 return;
384 kDebug( 1218 ) << "Kcminit phase 2 timeout";
385 kcmPhase2Done();
388 void KSMServer::finishStartup()
390 if( state != FinishingStartup )
391 return;
392 if( waitAutoStart2 || waitKcmInit2 )
393 return;
395 upAndRunning( "ready" );
397 state = Idle;
398 setupXIOErrorHandler(); // From now on handle X errors as normal shutdown.
401 bool KSMServer::checkStartupSuspend()
403 if( startupSuspendCount.isEmpty())
404 return true;
405 // wait for the phase to finish
406 if( !startupSuspendTimeoutTimer.isActive())
408 startupSuspendTimeoutTimer.setSingleShot( true );
409 startupSuspendTimeoutTimer.start( 10000 );
411 return false;
414 void KSMServer::suspendStartup( const QString &app )
416 if( !startupSuspendCount.contains( app ))
417 startupSuspendCount[ app ] = 0;
418 ++startupSuspendCount[ app ];
421 void KSMServer::resumeStartup( const QString &app )
423 if( !startupSuspendCount.contains( app ))
424 return;
425 if( --startupSuspendCount[ app ] == 0 ) {
426 startupSuspendCount.remove( app );
427 if( startupSuspendCount.isEmpty() && startupSuspendTimeoutTimer.isActive()) {
428 startupSuspendTimeoutTimer.stop();
429 resumeStartupInternal();
434 void KSMServer::startupSuspendTimeout()
436 kDebug( 1218 ) << "Startup suspend timeout:" << state;
437 resumeStartupInternal();
440 void KSMServer::resumeStartupInternal()
442 startupSuspendCount.clear();
443 switch( state ) {
444 case LaunchingWM:
445 autoStart0();
446 break;
447 case AutoStart0:
448 autoStart0Done();
449 break;
450 case AutoStart1:
451 autoStart1Done();
452 break;
453 case Restoring:
454 autoStart2();
455 break;
456 default:
457 kWarning( 1218 ) << "Unknown resume startup state" ;
458 break;
462 void KSMServer::publishProgress( int progress, bool max )
464 Q_UNUSED( progress );
465 Q_UNUSED( max );
466 // KSplash now goes away before restoring of session-saved apps starts, in order
467 // to make the startup visually faster, so this is not needed now. Also, there's
468 // no DBUS interface anymore.
469 #if 0
470 org::kde::KSplash ksplash("org.kde.ksplash", "/KSplash", QDBusConnection::sessionBus());
471 if(max)
472 ksplash.setMaxProgress(progress);
473 else
474 ksplash.setProgress(progress);
475 #endif
479 void KSMServer::upAndRunning( const QString& msg )
481 XEvent e;
482 e.xclient.type = ClientMessage;
483 e.xclient.message_type = XInternAtom( QX11Info::display(), "_KDE_SPLASH_PROGRESS", False );
484 e.xclient.display = QX11Info::display();
485 e.xclient.window = QX11Info::appRootWindow();
486 e.xclient.format = 8;
487 assert( strlen( msg.toLatin1()) < 20 );
488 strcpy( e.xclient.data.b, msg.toLatin1());
489 XSendEvent( QX11Info::display(), QX11Info::appRootWindow(), False, SubstructureNotifyMask, &e );