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 "server.h"
33#include "global.h"
34#include "client.h"
35#include "ksmserverinterfaceadaptor.h"
36
37#include <config-workspace.h>
38#include <config-unix.h> // HAVE_LIMITS_H
39#include <config-ksmserver.h>
40#include <pwd.h>
41#include <sys/types.h>
42#include <sys/param.h>
43#include <sys/stat.h>
44#ifdef HAVE_SYS_TIME_H
45#include <sys/time.h>
46#endif
47#include <sys/socket.h>
48#include <sys/un.h>
49
50#include <unistd.h>
51#include <stdlib.h>
52#include <signal.h>
53#include <time.h>
54#include <errno.h>
55#include <string.h>
56#include <assert.h>
57#include <fcntl.h>
58#include <kdefakes.h>
59
60#ifdef HAVE_LIMITS_H
61#include <limits.h>
62#endif
63
64#include <QFile>
65#include <QPushButton>
66#include <QRegExp>
67#include <QtDBus/QtDBus>
68#include <QSocketNotifier>
69
70#include <kaction.h>
71#include <kactioncollection.h>
72#include <kauthorized.h>
73#include <klocale.h>
74#include <kglobal.h>
75#include <kconfig.h>
76#include <kdesktopfile.h>
77#include <kstandarddirs.h>
78#include <kapplication.h>
79#include <ktemporaryfile.h>
80#include <kconfiggroup.h>
81#include <kprocess.h>
82#include <kdebug.h>
83#include <kshell.h>
84
85#include "server.moc"
86
87// must go after #include <config-ksmserver.h>
88#ifdef COMPILE_SCREEN_LOCKER
89#include "screenlocker/ksldapp.h"
90#endif
91
92#include <kworkspace/kdisplaymanager.h>
93#include <QX11Info>
94#include <krandom.h>
95#include <klauncher_iface.h>
96
97KSMServer* the_server = 0;
98
99KSMServer* KSMServer::self()
100{
101 return the_server;
102}
103
104/*! Utility function to execute a command on the local machine. Used
105 * to restart applications.
106 */
107KProcess* KSMServer::startApplication( const QStringList& cmd, const QString& clientMachine,
108 const QString& userId, bool wm )
109{
110 QStringList command = cmd;
111 if ( command.isEmpty() )
112 return NULL;
113 if ( !userId.isEmpty()) {
114 struct passwd* pw = getpwuid( getuid());
115 if( pw != NULL && userId != QString::fromLocal8Bit( pw->pw_name )) {
116 command.prepend( "--" );
117 command.prepend( userId );
118 command.prepend( "-u" );
119 command.prepend( KStandardDirs::findExe("kdesu") );
120 }
121 }
122 if ( !clientMachine.isEmpty() && clientMachine != "localhost" ) {
123 command.prepend( clientMachine );
124 command.prepend( xonCommand ); // "xon" by default
125 }
126
127// TODO this function actually should not use KProcess at all and use klauncher (kdeinit) instead.
128// Klauncher should also have support for tracking whether the launched process is still alive
129// or not, so this should be redone. For now, use KProcess for wm's, as they need to be tracked,
130// klauncher for the rest where ksmserver doesn't care.
131 if( wm ) {
132 KProcess* process = new KProcess( this );
133 *process << command;
134 // make it auto-delete
135 connect( process, SIGNAL(error(QProcess::ProcessError)), process, SLOT(deleteLater()));
136 connect( process, SIGNAL(finished(int,QProcess::ExitStatus)), process, SLOT(deleteLater()));
137 process->start();
138 return process;
139 } else {
140 int n = command.count();
141 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
142 QString app = command[0];
143 QStringList argList;
144 for ( int i=1; i < n; i++)
145 argList.append( command[i]);
146 klauncher.exec_blind(app, argList );
147 return NULL;
148 }
149}
150
151/*! Utility function to execute a command on the local machine. Used
152 * to discard session data
153 */
154void KSMServer::executeCommand( const QStringList& command )
155{
156 if ( command.isEmpty() )
157 return;
158
159 KProcess::execute( command );
160}
161
162IceAuthDataEntry *authDataEntries = 0;
163
164static KTemporaryFile *remTempFile = 0;
165
166static IceListenObj *listenObjs = 0;
167int numTransports = 0;
168static bool only_local = 0;
169
170static Bool HostBasedAuthProc ( char* /*hostname*/)
171{
172 if (only_local)
173 return true;
174 else
175 return false;
176}
177
178
179Status KSMRegisterClientProc (
180 SmsConn /* smsConn */,
181 SmPointer managerData,
182 char * previousId
183)
184{
185 KSMClient* client = (KSMClient*) managerData;
186 client->registerClient( previousId );
187 return 1;
188}
189
190void KSMInteractRequestProc (
191 SmsConn /* smsConn */,
192 SmPointer managerData,
193 int dialogType
194)
195{
196 the_server->interactRequest( (KSMClient*) managerData, dialogType );
197}
198
199void KSMInteractDoneProc (
200 SmsConn /* smsConn */,
201 SmPointer managerData,
202 Bool cancelShutdown
203)
204{
205 the_server->interactDone( (KSMClient*) managerData, cancelShutdown );
206}
207
208void KSMSaveYourselfRequestProc (
209 SmsConn smsConn ,
210 SmPointer /* managerData */,
211 int saveType,
212 Bool shutdown,
213 int interactStyle,
214 Bool fast,
215 Bool global
216)
217{
218 if ( shutdown ) {
219 the_server->shutdown( fast ?
220 KWorkSpace::ShutdownConfirmNo :
221 KWorkSpace::ShutdownConfirmDefault,
222 KWorkSpace::ShutdownTypeDefault,
223 KWorkSpace::ShutdownModeDefault );
224 } else if ( !global ) {
225 SmsSaveYourself( smsConn, saveType, false, interactStyle, fast );
226 SmsSaveComplete( smsConn );
227 }
228 // else checkpoint only, ksmserver does not yet support this
229 // mode. Will come for KDE 3.1
230}
231
232void KSMSaveYourselfPhase2RequestProc (
233 SmsConn /* smsConn */,
234 SmPointer managerData
235)
236{
237 the_server->phase2Request( (KSMClient*) managerData );
238}
239
240void KSMSaveYourselfDoneProc (
241 SmsConn /* smsConn */,
242 SmPointer managerData,
243 Bool success
244)
245{
246 the_server->saveYourselfDone( (KSMClient*) managerData, success );
247}
248
249void KSMCloseConnectionProc (
250 SmsConn smsConn,
251 SmPointer managerData,
252 int count,
253 char ** reasonMsgs
254)
255{
256 the_server->deleteClient( ( KSMClient* ) managerData );
257 if ( count )
258 SmFreeReasons( count, reasonMsgs );
259 IceConn iceConn = SmsGetIceConnection( smsConn );
260 SmsCleanUp( smsConn );
261 IceSetShutdownNegotiation (iceConn, False);
262 IceCloseConnection( iceConn );
263}
264
265void KSMSetPropertiesProc (
266 SmsConn /* smsConn */,
267 SmPointer managerData,
268 int numProps,
269 SmProp ** props
270)
271{
272 KSMClient* client = ( KSMClient* ) managerData;
273 for ( int i = 0; i < numProps; i++ ) {
274 SmProp *p = client->property( props[i]->name );
275 if ( p ) {
276 client->properties.removeAll( p );
277 SmFreeProperty( p );
278 }
279 client->properties.append( props[i] );
280 if ( !qstrcmp( props[i]->name, SmProgram ) )
281 the_server->clientSetProgram( client );
282 }
283
284 if ( numProps )
285 free( props );
286
287}
288
289void KSMDeletePropertiesProc (
290 SmsConn /* smsConn */,
291 SmPointer managerData,
292 int numProps,
293 char ** propNames
294)
295{
296 KSMClient* client = ( KSMClient* ) managerData;
297 for ( int i = 0; i < numProps; i++ ) {
298 SmProp *p = client->property( propNames[i] );
299 if ( p ) {
300 client->properties.removeAll( p );
301 SmFreeProperty( p );
302 }
303 }
304}
305
306void KSMGetPropertiesProc (
307 SmsConn smsConn,
308 SmPointer managerData
309)
310{
311 KSMClient* client = ( KSMClient* ) managerData;
312 SmProp** props = new SmProp*[client->properties.count()];
313 int i = 0;
314 foreach( SmProp *prop, client->properties )
315 props[i++] = prop;
316
317 SmsReturnProperties( smsConn, i, props );
318 delete [] props;
319}
320
321
322class KSMListener : public QSocketNotifier
323{
324public:
325 KSMListener( IceListenObj obj )
326 : QSocketNotifier( IceGetListenConnectionNumber( obj ),
327 QSocketNotifier::Read )
328{
329 listenObj = obj;
330}
331
332 IceListenObj listenObj;
333};
334
335class KSMConnection : public QSocketNotifier
336{
337 public:
338 KSMConnection( IceConn conn )
339 : QSocketNotifier( IceConnectionNumber( conn ),
340 QSocketNotifier::Read )
341 {
342 iceConn = conn;
343 }
344
345 IceConn iceConn;
346};
347
348
349/* for printing hex digits */
350static void fprintfhex (FILE *fp, unsigned int len, char *cp)
351{
352 static const char hexchars[] = "0123456789abcdef";
353
354 for (; len > 0; len--, cp++) {
355 unsigned char s = *cp;
356 putc(hexchars[s >> 4], fp);
357 putc(hexchars[s & 0x0f], fp);
358 }
359}
360
361/*
362 * We use temporary files which contain commands to add/remove entries from
363 * the .ICEauthority file.
364 */
365static void write_iceauth (FILE *addfp, FILE *removefp, IceAuthDataEntry *entry)
366{
367 fprintf (addfp,
368 "add %s \"\" %s %s ",
369 entry->protocol_name,
370 entry->network_id,
371 entry->auth_name);
372 fprintfhex (addfp, entry->auth_data_length, entry->auth_data);
373 fprintf (addfp, "\n");
374
375 fprintf (removefp,
376 "remove protoname=%s protodata=\"\" netid=%s authname=%s\n",
377 entry->protocol_name,
378 entry->network_id,
379 entry->auth_name);
380}
381
382
383#define MAGIC_COOKIE_LEN 16
384
385Status SetAuthentication_local (int count, IceListenObj *listenObjs)
386{
387 int i;
388 for (i = 0; i < count; i ++) {
389 char *prot = IceGetListenConnectionString(listenObjs[i]);
390 if (!prot) continue;
391 char *host = strchr(prot, '/');
392 char *sock = 0;
393 if (host) {
394 *host=0;
395 host++;
396 sock = strchr(host, ':');
397 if (sock) {
398 *sock = 0;
399 sock++;
400 }
401 }
402 kDebug( 1218 ) << "KSMServer: SetAProc_loc: conn " << (unsigned)i << ", prot=" << prot << ", file=" << sock;
403 if (sock && !strcmp(prot, "local")) {
404 chmod(sock, 0700);
405 }
406 IceSetHostBasedAuthProc (listenObjs[i], HostBasedAuthProc);
407 free(prot);
408 }
409 return 1;
410}
411
412Status SetAuthentication (int count, IceListenObj *listenObjs,
413 IceAuthDataEntry **authDataEntries)
414{
415 KTemporaryFile addTempFile;
416 remTempFile = new KTemporaryFile;
417
418 if (!addTempFile.open() || !remTempFile->open())
419 return 0;
420
421 if ((*authDataEntries = (IceAuthDataEntry *) malloc (
422 count * 2 * sizeof (IceAuthDataEntry))) == NULL)
423 return 0;
424
425 FILE *addAuthFile = fopen(QFile::encodeName(addTempFile.fileName()), "r+");
426 FILE *remAuthFile = fopen(QFile::encodeName(remTempFile->fileName()), "r+");
427
428 for (int i = 0; i < numTransports * 2; i += 2) {
429 (*authDataEntries)[i].network_id =
430 IceGetListenConnectionString (listenObjs[i/2]);
431 (*authDataEntries)[i].protocol_name = (char *) "ICE";
432 (*authDataEntries)[i].auth_name = (char *) "MIT-MAGIC-COOKIE-1";
433
434 (*authDataEntries)[i].auth_data =
435 IceGenerateMagicCookie (MAGIC_COOKIE_LEN);
436 (*authDataEntries)[i].auth_data_length = MAGIC_COOKIE_LEN;
437
438 (*authDataEntries)[i+1].network_id =
439 IceGetListenConnectionString (listenObjs[i/2]);
440 (*authDataEntries)[i+1].protocol_name = (char *) "XSMP";
441 (*authDataEntries)[i+1].auth_name = (char *) "MIT-MAGIC-COOKIE-1";
442
443 (*authDataEntries)[i+1].auth_data =
444 IceGenerateMagicCookie (MAGIC_COOKIE_LEN);
445 (*authDataEntries)[i+1].auth_data_length = MAGIC_COOKIE_LEN;
446
447 write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i]);
448 write_iceauth (addAuthFile, remAuthFile, &(*authDataEntries)[i+1]);
449
450 IceSetPaAuthData (2, &(*authDataEntries)[i]);
451
452 IceSetHostBasedAuthProc (listenObjs[i/2], HostBasedAuthProc);
453 }
454 fclose(addAuthFile);
455 fclose(remAuthFile);
456
457 QString iceAuth = KGlobal::dirs()->findExe("iceauth");
458 if (iceAuth.isEmpty())
459 {
460 qWarning("KSMServer: could not find iceauth");
461 return 0;
462 }
463
464 KProcess p;
465 p << iceAuth << "source" << addTempFile.fileName();
466 p.execute();
467
468 return (1);
469}
470
471/*
472 * Free up authentication data.
473 */
474void FreeAuthenticationData(int count, IceAuthDataEntry *authDataEntries)
475{
476 /* Each transport has entries for ICE and XSMP */
477 if (only_local)
478 return;
479
480 for (int i = 0; i < count * 2; i++) {
481 free (authDataEntries[i].network_id);
482 free (authDataEntries[i].auth_data);
483 }
484
485 free (authDataEntries);
486
487 QString iceAuth = KGlobal::dirs()->findExe("iceauth");
488 if (iceAuth.isEmpty())
489 {
490 qWarning("KSMServer: could not find iceauth");
491 return;
492 }
493
494 if (remTempFile)
495 {
496 KProcess p;
497 p << iceAuth << "source" << remTempFile->fileName();
498 p.execute();
499 }
500
501 delete remTempFile;
502 remTempFile = 0;
503}
504
505static int Xio_ErrorHandler( Display * )
506{
507 qWarning("ksmserver: Fatal IO error: client killed");
508
509 // Don't do anything that might require the X connection
510 if (the_server)
511 {
512 KSMServer *server = the_server;
513 the_server = 0;
514 server->cleanUp();
515 // Don't delete server!!
516 }
517
518 exit(0); // Don't report error, it's not our fault.
519 return 0; // Bogus return value, notreached
520}
521
522void KSMServer::setupXIOErrorHandler()
523{
524 XSetIOErrorHandler(Xio_ErrorHandler);
525}
526
527static void sighandler(int sig)
528{
529 if (sig == SIGHUP) {
530 signal(SIGHUP, sighandler);
531 return;
532 }
533
534 if (the_server)
535 {
536 KSMServer *server = the_server;
537 the_server = 0;
538 server->cleanUp();
539 delete server;
540 }
541
542 if (kapp)
543 kapp->quit();
544 //::exit(0);
545}
546
547
548void KSMWatchProc ( IceConn iceConn, IcePointer client_data, Bool opening, IcePointer* watch_data)
549{
550 KSMServer* ds = ( KSMServer*) client_data;
551
552 if (opening) {
553 *watch_data = (IcePointer) ds->watchConnection( iceConn );
554 }
555 else {
556 ds->removeConnection( (KSMConnection*) *watch_data );
557 }
558}
559
560static Status KSMNewClientProc ( SmsConn conn, SmPointer manager_data,
561 unsigned long* mask_ret, SmsCallbacks* cb, char** failure_reason_ret)
562{
563 *failure_reason_ret = 0;
564
565 void* client = ((KSMServer*) manager_data )->newClient( conn );
566
567 cb->register_client.callback = KSMRegisterClientProc;
568 cb->register_client.manager_data = client;
569 cb->interact_request.callback = KSMInteractRequestProc;
570 cb->interact_request.manager_data = client;
571 cb->interact_done.callback = KSMInteractDoneProc;
572 cb->interact_done.manager_data = client;
573 cb->save_yourself_request.callback = KSMSaveYourselfRequestProc;
574 cb->save_yourself_request.manager_data = client;
575 cb->save_yourself_phase2_request.callback = KSMSaveYourselfPhase2RequestProc;
576 cb->save_yourself_phase2_request.manager_data = client;
577 cb->save_yourself_done.callback = KSMSaveYourselfDoneProc;
578 cb->save_yourself_done.manager_data = client;
579 cb->close_connection.callback = KSMCloseConnectionProc;
580 cb->close_connection.manager_data = client;
581 cb->set_properties.callback = KSMSetPropertiesProc;
582 cb->set_properties.manager_data = client;
583 cb->delete_properties.callback = KSMDeletePropertiesProc;
584 cb->delete_properties.manager_data = client;
585 cb->get_properties.callback = KSMGetPropertiesProc;
586 cb->get_properties.manager_data = client;
587
588 *mask_ret = SmsRegisterClientProcMask |
589 SmsInteractRequestProcMask |
590 SmsInteractDoneProcMask |
591 SmsSaveYourselfRequestProcMask |
592 SmsSaveYourselfP2RequestProcMask |
593 SmsSaveYourselfDoneProcMask |
594 SmsCloseConnectionProcMask |
595 SmsSetPropertiesProcMask |
596 SmsDeletePropertiesProcMask |
597 SmsGetPropertiesProcMask;
598 return 1;
599}
600
601
602#ifdef HAVE__ICETRANSNOLISTEN
603extern "C" int _IceTransNoListen(const char * protocol);
604#endif
605
606KSMServer::KSMServer( const QString& windowManager, bool _only_local, bool lockscreen )
607 : wmProcess( NULL )
608 , sessionGroup( "" )
609 , logoutEffectWidget( NULL )
610{
611#ifdef COMPILE_SCREEN_LOCKER
612 KGlobal::locale()->insertCatalog(QLatin1String( "libkworkspace" ));
613
614 ScreenLocker::KSldApp::self();
615 if (lockscreen) {
616 ScreenLocker::KSldApp::self()->lock();
617 }
618#else
619 Q_UNUSED(lockscreen)
620#endif
621
622 new KSMServerInterfaceAdaptor( this );
623 QDBusConnection::sessionBus().registerObject("/KSMServer", this);
624 klauncherSignals = new OrgKdeKLauncherInterface(QLatin1String("org.kde.klauncher"),
625 QLatin1String("/KLauncher"), QDBusConnection::sessionBus());
626 kcminitSignals = NULL;
627 the_server = this;
628 clean = false;
629
630 shutdownType = KWorkSpace::ShutdownTypeNone;
631
632 state = Idle;
633 dialogActive = false;
634 saveSession = false;
635 wmPhase1WaitingCount = 0;
636 KConfigGroup config(KGlobal::config(), "General");
637 clientInteracting = 0;
638 xonCommand = config.readEntry( "xonCommand", "xon" );
639
640 KGlobal::dirs()->addResourceType( "windowmanagers", "data", "ksmserver/windowmanagers" );
641 selectWm( windowManager );
642
643 connect( &startupSuspendTimeoutTimer, SIGNAL(timeout()), SLOT(startupSuspendTimeout()));
644 connect( &pendingShutdown, SIGNAL(timeout()), SLOT(pendingShutdownTimeout()));
645
646 only_local = _only_local;
647#ifdef HAVE__ICETRANSNOLISTEN
648 if (only_local)
649 _IceTransNoListen("tcp");
650#else
651 only_local = false;
652#endif
653
654 char errormsg[256];
655 if (!SmsInitialize ( (char*) KSMVendorString, (char*) KSMReleaseString,
656 KSMNewClientProc,
657 (SmPointer) this,
658 HostBasedAuthProc, 256, errormsg ) ) {
659
660 qWarning("KSMServer: could not register XSM protocol");
661 }
662
663 if (!IceListenForConnections (&numTransports, &listenObjs,
664 256, errormsg))
665 {
666 qWarning("KSMServer: Error listening for connections: %s", errormsg);
667 qWarning("KSMServer: Aborting.");
668 exit(1);
669 }
670
671 {
672 // publish available transports.
673 QByteArray fName = QFile::encodeName(KStandardDirs::locateLocal("socket", "KSMserver"));
674 QString display = ::getenv("DISPLAY");
675 // strip the screen number from the display
676 display.replace(QRegExp("\\.[0-9]+$"), "");
677 int i;
678 while( (i = display.indexOf(':')) >= 0)
679 display[i] = '_';
680 while( (i = display.indexOf('/')) >= 0)
681 display[i] = '_';
682
683 fName += '_'+display.toLocal8Bit();
684 FILE *f;
685 f = ::fopen(fName.data(), "w+");
686 if (!f)
687 {
688 qWarning("KSMServer: cannot open %s: %s", fName.data(), strerror(errno));
689 qWarning("KSMServer: Aborting.");
690 exit(1);
691 }
692 char* session_manager = IceComposeNetworkIdList(numTransports, listenObjs);
693 fprintf(f, "%s\n%i\n", session_manager, getpid());
694 fclose(f);
695 setenv( "SESSION_MANAGER", session_manager, true );
696
697 // Pass env. var to kdeinit.
698 org::kde::KLauncher klauncher("org.kde.klauncher", "/KLauncher", QDBusConnection::sessionBus());
699 klauncher.setLaunchEnv( "SESSION_MANAGER", (const char*) session_manager );
700
701 free(session_manager);
702 }
703
704 if (only_local) {
705 if (!SetAuthentication_local(numTransports, listenObjs))
706 qFatal("KSMSERVER: authentication setup failed.");
707 } else {
708 if (!SetAuthentication(numTransports, listenObjs, &authDataEntries))
709 qFatal("KSMSERVER: authentication setup failed.");
710 }
711
712 IceAddConnectionWatch (KSMWatchProc, (IcePointer) this);
713
714 KSMListener* con;
715 for ( int i = 0; i < numTransports; i++) {
716 fcntl( IceGetListenConnectionNumber( listenObjs[i] ), F_SETFD, FD_CLOEXEC );
717 con = new KSMListener( listenObjs[i] );
718 listener.append( con );
719 connect( con, SIGNAL(activated(int)), this, SLOT(newConnection(int)) );
720 }
721
722 signal(SIGHUP, sighandler);
723 signal(SIGTERM, sighandler);
724 signal(SIGINT, sighandler);
725 signal(SIGPIPE, SIG_IGN);
726
727 connect( &protectionTimer, SIGNAL(timeout()), this, SLOT(protectionTimeout()) );
728 connect( &restoreTimer, SIGNAL(timeout()), this, SLOT(tryRestoreNext()) );
729 connect( qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanUp()) );
730}
731
732KSMServer::~KSMServer()
733{
734 qDeleteAll( listener );
735 the_server = 0;
736 cleanUp();
737}
738
739void KSMServer::cleanUp()
740{
741 if (clean) return;
742 clean = true;
743 IceFreeListenObjs (numTransports, listenObjs);
744
745 QByteArray fName = QFile::encodeName(KStandardDirs::locateLocal("socket", "KSMserver"));
746 QString display = QString::fromLocal8Bit(::getenv("DISPLAY"));
747 // strip the screen number from the display
748 display.replace(QRegExp("\\.[0-9]+$"), "");
749 int i;
750 while( (i = display.indexOf(':')) >= 0)
751 display[i] = '_';
752 while( (i = display.indexOf('/')) >= 0)
753 display[i] = '_';
754
755 fName += '_'+display.toLocal8Bit();
756 ::unlink(fName.data());
757
758 FreeAuthenticationData(numTransports, authDataEntries);
759 signal(SIGTERM, SIG_DFL);
760 signal(SIGINT, SIG_DFL);
761
762 KDisplayManager().shutdown( shutdownType, shutdownMode, bootOption );
763}
764
765
766
767void* KSMServer::watchConnection( IceConn iceConn )
768{
769 KSMConnection* conn = new KSMConnection( iceConn );
770 connect( conn, SIGNAL(activated(int)), this, SLOT(processData(int)) );
771 return (void*) conn;
772}
773
774void KSMServer::removeConnection( KSMConnection* conn )
775{
776 delete conn;
777}
778
779
780/*!
781 Called from our IceIoErrorHandler
782 */
783void KSMServer::ioError( IceConn /*iceConn*/ )
784{
785}
786
787void KSMServer::processData( int /*socket*/ )
788{
789 IceConn iceConn = ((KSMConnection*)sender())->iceConn;
790 IceProcessMessagesStatus status = IceProcessMessages( iceConn, 0, 0 );
791 if ( status == IceProcessMessagesIOError ) {
792 IceSetShutdownNegotiation( iceConn, False );
793 QList<KSMClient*>::iterator it = clients.begin();
794 QList<KSMClient*>::iterator const itEnd = clients.end();
795 while ( ( it != itEnd ) && *it && ( SmsGetIceConnection( ( *it )->connection() ) != iceConn ) )
796 ++it;
797 if ( ( it != itEnd ) && *it ) {
798 SmsConn smsConn = (*it)->connection();
799 deleteClient( *it );
800 SmsCleanUp( smsConn );
801 }
802 (void) IceCloseConnection( iceConn );
803 }
804}
805
806KSMClient* KSMServer::newClient( SmsConn conn )
807{
808 KSMClient* client = new KSMClient( conn );
809 clients.append( client );
810 return client;
811}
812
813void KSMServer::deleteClient( KSMClient* client )
814{
815 if ( !clients.contains( client ) ) // paranoia
816 return;
817 clients.removeAll( client );
818 clientsToKill.removeAll( client );
819 clientsToSave.removeAll( client );
820 if ( client == clientInteracting ) {
821 clientInteracting = 0;
822 handlePendingInteractions();
823 }
824 delete client;
825 if ( state == Shutdown || state == Checkpoint || state == ClosingSubSession )
826 completeShutdownOrCheckpoint();
827 if ( state == Killing )
828 completeKilling();
829 else if ( state == KillingSubSession )
830 completeKillingSubSession();
831 if ( state == KillingWM )
832 completeKillingWM();
833}
834
835void KSMServer::newConnection( int /*socket*/ )
836{
837 IceAcceptStatus status;
838 IceConn iceConn = IceAcceptConnection( ((KSMListener*)sender())->listenObj, &status);
839 if( iceConn == NULL )
840 return;
841 IceSetShutdownNegotiation( iceConn, False );
842 IceConnectStatus cstatus;
843 while ((cstatus = IceConnectionStatus (iceConn))==IceConnectPending) {
844 (void) IceProcessMessages( iceConn, 0, 0 );
845 }
846
847 if (cstatus != IceConnectAccepted) {
848 if (cstatus == IceConnectIOError)
849 kDebug( 1218 ) << "IO error opening ICE Connection!";
850 else
851 kDebug( 1218 ) << "ICE Connection rejected!";
852 (void )IceCloseConnection (iceConn);
853 return;
854 }
855
856 // don't leak the fd
857 fcntl( IceConnectionNumber(iceConn), F_SETFD, FD_CLOEXEC );
858}
859
860
861QString KSMServer::currentSession()
862{
863 if ( sessionGroup.startsWith( "Session: " ) )
864 return sessionGroup.mid( 9 );
865 return ""; // empty, not null, since used for KConfig::setGroup
866}
867
868void KSMServer::discardSession()
869{
870 KConfigGroup config(KGlobal::config(), sessionGroup );
871 int count = config.readEntry( "count", 0 );
872 foreach ( KSMClient *c, clients ) {
873 QStringList discardCommand = c->discardCommand();
874 if ( discardCommand.isEmpty())
875 continue;
876 // check that non of the old clients used the exactly same
877 // discardCommand before we execute it. This used to be the
878 // case up to KDE and Qt < 3.1
879 int i = 1;
880 while ( i <= count &&
881 config.readPathEntry( QString("discardCommand") + QString::number(i), QStringList() ) != discardCommand )
882 i++;
883 if ( i <= count )
884 executeCommand( discardCommand );
885 }
886}
887
888void KSMServer::storeSession()
889{
890 KSharedConfig::Ptr config = KGlobal::config();
891 config->reparseConfiguration(); // config may have changed in the KControl module
892 KConfigGroup generalGroup(config, "General");
893 excludeApps = generalGroup.readEntry( "excludeApps" ).toLower().split( QRegExp( "[,:]" ), QString::SkipEmptyParts );
894 KConfigGroup configSessionGroup(config, sessionGroup);
895 int count = configSessionGroup.readEntry( "count", 0 );
896 for ( int i = 1; i <= count; i++ ) {
897 QStringList discardCommand = configSessionGroup.readPathEntry( QLatin1String("discardCommand") + QString::number(i), QStringList() );
898 if ( discardCommand.isEmpty())
899 continue;
900 // check that non of the new clients uses the exactly same
901 // discardCommand before we execute it. This used to be the
902 // case up to KDE and Qt < 3.1
903 QList<KSMClient*>::iterator it = clients.begin();
904 QList<KSMClient*>::iterator const itEnd = clients.end();
905 while ( ( it != itEnd ) && *it && (discardCommand != ( *it )->discardCommand() ) )
906 ++it;
907 if ( ( it != itEnd ) && *it )
908 continue;
909 executeCommand( discardCommand );
910 }
911 config->deleteGroup( sessionGroup ); //### does not work with global config object...
912 KConfigGroup cg( config, sessionGroup);
913 count = 0;
914
915 if (state != ClosingSubSession) {
916 // put the wm first
917 foreach ( KSMClient *c, clients )
918 if ( c->program() == wm ) {
919 clients.removeAll( c );
920 clients.prepend( c );
921 break;
922 }
923 }
924
925 foreach ( KSMClient *c, clients ) {
926 int restartHint = c->restartStyleHint();
927 if (restartHint == SmRestartNever)
928 continue;
929 QString program = c->program();
930 QStringList restartCommand = c->restartCommand();
931 if (program.isEmpty() && restartCommand.isEmpty())
932 continue;
933 if (state == ClosingSubSession && ! clientsToSave.contains(c))
934 continue;
935
936 // 'program' might be (mostly) fullpath, or (sometimes) just the name.
937 // 'name' is just the name.
938 QFileInfo info(program);
939 const QString& name = info.fileName();
940
941 if ( excludeApps.contains(program.toLower()) ||
942 excludeApps.contains(name.toLower()) ) {
943 continue;
944 }
945
946 count++;
947 QString n = QString::number(count);
948 cg.writeEntry( QString("program")+n, program );
949 cg.writeEntry( QString("clientId")+n, c->clientId() );
950 cg.writeEntry( QString("restartCommand")+n, restartCommand );
951 cg.writePathEntry( QString("discardCommand")+n, c->discardCommand() );
952 cg.writeEntry( QString("restartStyleHint")+n, restartHint );
953 cg.writeEntry( QString("userId")+n, c->userId() );
954 cg.writeEntry( QString("wasWm")+n, isWM( c ));
955 }
956 cg.writeEntry( "count", count );
957
958 KConfigGroup cg2( config, "General");
959 cg2.writeEntry( "screenCount", ScreenCount(QX11Info::display()));
960
961 storeLegacySession(config.data());
962 config->sync();
963}
964
965QStringList KSMServer::sessionList()
966{
967 QStringList sessions ( "default" );
968 KSharedConfig::Ptr config = KGlobal::config();
969 const QStringList groups = config->groupList();
970 for ( QStringList::ConstIterator it = groups.constBegin(); it != groups.constEnd(); ++it )
971 if ( (*it).startsWith( "Session: " ) )
972 sessions << (*it).mid( 9 );
973 return sessions;
974}
975
976bool KSMServer::isWM( const KSMClient* client ) const
977{
978 return isWM( client->program());
979}
980
981bool KSMServer::isWM( const QString& command ) const
982{
983 return command == wm;
984}
985
986bool KSMServer::defaultSession() const
987{
988 return sessionGroup.isEmpty();
989}
990
991// selection logic:
992// - $KDEWM is set - use that
993// - a wm is selected using the kcm - use that
994// - if that fails, just use KWin
995void KSMServer::selectWm( const QString& kdewm )
996{
997 wm = "kwin"; // defaults
998 wmCommands = ( QStringList() << "kwin" );
999 if( qstrcmp( getenv( "KDE_FAILSAFE" ), "1" ) == 0 )
1000 return; // failsafe, force kwin
1001 if( !kdewm.isEmpty())
1002 {
1003 wmCommands = ( QStringList() << kdewm );
1004 wm = kdewm;
1005 return;
1006 }
1007 KConfigGroup config(KGlobal::config(), "General");
1008 QString cfgwm = config.readEntry( "windowManager", "kwin" );
1009 KDesktopFile file( "windowmanagers", cfgwm + ".desktop" );
1010 if( file.noDisplay())
1011 return;
1012 if( !file.tryExec())
1013 return;
1014 QString testexec = file.desktopGroup().readEntry( "X-KDE-WindowManagerTestExec" );
1015 if( !testexec.isEmpty())
1016 {
1017 KProcess proc;
1018 proc.setShellCommand( testexec );
1019 if( proc.execute() != 0 )
1020 return;
1021 }
1022 QStringList cfgWmCommands = KShell::splitArgs( file.desktopGroup().readEntry( "Exec" ));
1023 if( cfgWmCommands.isEmpty())
1024 return;
1025 QString smname = file.desktopGroup().readEntry( "X-KDE-WindowManagerId" );
1026 // ok
1027 wm = smname.isEmpty() ? cfgwm : smname;
1028 wmCommands = cfgWmCommands;
1029}
1030
1031void KSMServer::wmChanged()
1032{
1033 KGlobal::config()->reparseConfiguration();
1034 selectWm( "" );
1035}
1036
1037void KSMServer::setupShortcuts()
1038{
1039 if (KAuthorized::authorize("logout")) {
1040 KActionCollection* actionCollection = new KActionCollection(this);
1041 KAction* a;
1042 a = actionCollection->addAction("Log Out");
1043 a->setText(i18n("Log Out"));
1044 a->setGlobalShortcut(KShortcut(Qt::ALT+Qt::CTRL+Qt::Key_Delete));
1045 connect(a, SIGNAL(triggered(bool)), SLOT(defaultLogout()));
1046
1047 a = actionCollection->addAction("Log Out Without Confirmation");
1048 a->setText(i18n("Log Out Without Confirmation"));
1049 a->setGlobalShortcut(KShortcut(Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_Delete));
1050 connect(a, SIGNAL(triggered(bool)), SLOT(logoutWithoutConfirmation()));
1051
1052 a = actionCollection->addAction("Halt Without Confirmation");
1053 a->setText(i18n("Halt Without Confirmation"));
1054 a->setGlobalShortcut(KShortcut(Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageDown));
1055 connect(a, SIGNAL(triggered(bool)), SLOT(haltWithoutConfirmation()));
1056
1057 a = actionCollection->addAction("Reboot Without Confirmation");
1058 a->setText(i18n("Reboot Without Confirmation"));
1059 a->setGlobalShortcut(KShortcut(Qt::ALT+Qt::CTRL+Qt::SHIFT+Qt::Key_PageUp));
1060 connect(a, SIGNAL(triggered(bool)), SLOT(rebootWithoutConfirmation()));
1061 }
1062}
1063
1064void KSMServer::defaultLogout()
1065{
1066 shutdown(KWorkSpace::ShutdownConfirmYes, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault);
1067}
1068
1069void KSMServer::logoutWithoutConfirmation()
1070{
1071 shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeNone, KWorkSpace::ShutdownModeDefault);
1072}
1073
1074void KSMServer::haltWithoutConfirmation()
1075{
1076 shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeHalt, KWorkSpace::ShutdownModeDefault);
1077}
1078
1079void KSMServer::rebootWithoutConfirmation()
1080{
1081 shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeReboot, KWorkSpace::ShutdownModeDefault);
1082}
1083