1 | /***************************************************************** |
2 | ksmserver - the KDE session management server |
3 | |
4 | Copyright 2000 Matthias Ettrich <ettrich@kde.org> |
5 | Copyright 2005 Lubos Lunak <l.lunak@kde.org> |
6 | |
7 | relatively small extensions by Oswald Buddenhagen <ob6@inf.tu-dresden.de> |
8 | |
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> |
12 | |
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: |
19 | |
20 | The above copyright notice and this permission notice shall be included in |
21 | all copies or substantial portions of the Software. |
22 | |
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. |
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 |
88 | static QTime t; |
89 | #endif |
90 | |
91 | /*! Restores the previous session. Ensures the window manager is |
92 | running (if specified). |
93 | */ |
94 | void 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 | */ |
137 | void 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 | |
154 | void 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 | |
167 | void 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 | |
191 | void 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 | |
212 | void 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 | |
226 | void 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 | |
254 | void 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 | |
265 | void KSMServer::kcmPhase1Timeout() |
266 | { |
267 | if( state != KcmInitPhase1 ) |
268 | return; |
269 | kDebug( 1218 ) << "Kcminit phase 1 timeout" ; |
270 | kcmPhase1Done(); |
271 | } |
272 | |
273 | void 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 | |
285 | void 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 | |
307 | void KSMServer::clientRegistered( const char* previousId ) |
308 | { |
309 | if ( previousId && lastIdStarted == previousId ) |
310 | tryRestoreNext(); |
311 | } |
312 | |
313 | void 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 | |
367 | void 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 | |
408 | void 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 | |
432 | void 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 | |
442 | void 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 | |
456 | void KSMServer::kcmPhase2Timeout() |
457 | { |
458 | if( !waitKcmInit2 ) |
459 | return; |
460 | kDebug( 1218 ) << "Kcminit phase 2 timeout" ; |
461 | kcmPhase2Done(); |
462 | } |
463 | |
464 | void 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 | |
480 | bool 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 | |
493 | void KSMServer::suspendStartup( const QString &app ) |
494 | { |
495 | if( !startupSuspendCount.contains( app )) |
496 | startupSuspendCount[ app ] = 0; |
497 | ++startupSuspendCount[ app ]; |
498 | } |
499 | |
500 | void 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 | |
513 | void KSMServer::startupSuspendTimeout() |
514 | { |
515 | kDebug( 1218 ) << "Startup suspend timeout:" << state; |
516 | resumeStartupInternal(); |
517 | } |
518 | |
519 | void 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 | |
541 | void 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 | |
554 | void 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 | |