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 "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 | |
97 | KSMServer* the_server = 0; |
98 | |
99 | KSMServer* 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 | */ |
107 | KProcess* 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 | */ |
154 | void KSMServer::executeCommand( const QStringList& command ) |
155 | { |
156 | if ( command.isEmpty() ) |
157 | return; |
158 | |
159 | KProcess::execute( command ); |
160 | } |
161 | |
162 | IceAuthDataEntry *authDataEntries = 0; |
163 | |
164 | static KTemporaryFile *remTempFile = 0; |
165 | |
166 | static IceListenObj *listenObjs = 0; |
167 | int numTransports = 0; |
168 | static bool only_local = 0; |
169 | |
170 | static Bool HostBasedAuthProc ( char* /*hostname*/) |
171 | { |
172 | if (only_local) |
173 | return true; |
174 | else |
175 | return false; |
176 | } |
177 | |
178 | |
179 | Status 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 | |
190 | void KSMInteractRequestProc ( |
191 | SmsConn /* smsConn */, |
192 | SmPointer managerData, |
193 | int dialogType |
194 | ) |
195 | { |
196 | the_server->interactRequest( (KSMClient*) managerData, dialogType ); |
197 | } |
198 | |
199 | void KSMInteractDoneProc ( |
200 | SmsConn /* smsConn */, |
201 | SmPointer managerData, |
202 | Bool cancelShutdown |
203 | ) |
204 | { |
205 | the_server->interactDone( (KSMClient*) managerData, cancelShutdown ); |
206 | } |
207 | |
208 | void 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 | |
232 | void KSMSaveYourselfPhase2RequestProc ( |
233 | SmsConn /* smsConn */, |
234 | SmPointer managerData |
235 | ) |
236 | { |
237 | the_server->phase2Request( (KSMClient*) managerData ); |
238 | } |
239 | |
240 | void KSMSaveYourselfDoneProc ( |
241 | SmsConn /* smsConn */, |
242 | SmPointer managerData, |
243 | Bool success |
244 | ) |
245 | { |
246 | the_server->saveYourselfDone( (KSMClient*) managerData, success ); |
247 | } |
248 | |
249 | void 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 | |
265 | void 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 | |
289 | void 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 | |
306 | void 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 | |
322 | class KSMListener : public QSocketNotifier |
323 | { |
324 | public: |
325 | KSMListener( IceListenObj obj ) |
326 | : QSocketNotifier( IceGetListenConnectionNumber( obj ), |
327 | QSocketNotifier::Read ) |
328 | { |
329 | listenObj = obj; |
330 | } |
331 | |
332 | IceListenObj listenObj; |
333 | }; |
334 | |
335 | class 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 */ |
350 | static 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 | */ |
365 | static 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 | |
385 | Status 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 | |
412 | Status 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 | */ |
474 | void 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 | |
505 | static 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 | |
522 | void KSMServer::setupXIOErrorHandler() |
523 | { |
524 | XSetIOErrorHandler(Xio_ErrorHandler); |
525 | } |
526 | |
527 | static 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 | |
548 | void 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 | |
560 | static 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 |
603 | extern "C" int _IceTransNoListen(const char * protocol); |
604 | #endif |
605 | |
606 | KSMServer::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 | |
732 | KSMServer::~KSMServer() |
733 | { |
734 | qDeleteAll( listener ); |
735 | the_server = 0; |
736 | cleanUp(); |
737 | } |
738 | |
739 | void 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 | |
767 | void* 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 | |
774 | void KSMServer::removeConnection( KSMConnection* conn ) |
775 | { |
776 | delete conn; |
777 | } |
778 | |
779 | |
780 | /*! |
781 | Called from our IceIoErrorHandler |
782 | */ |
783 | void KSMServer::ioError( IceConn /*iceConn*/ ) |
784 | { |
785 | } |
786 | |
787 | void 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 | |
806 | KSMClient* KSMServer::newClient( SmsConn conn ) |
807 | { |
808 | KSMClient* client = new KSMClient( conn ); |
809 | clients.append( client ); |
810 | return client; |
811 | } |
812 | |
813 | void 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 | |
835 | void 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 | |
861 | QString 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 | |
868 | void 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 | |
888 | void 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 | |
965 | QStringList 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 | |
976 | bool KSMServer::isWM( const KSMClient* client ) const |
977 | { |
978 | return isWM( client->program()); |
979 | } |
980 | |
981 | bool KSMServer::isWM( const QString& command ) const |
982 | { |
983 | return command == wm; |
984 | } |
985 | |
986 | bool 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 |
995 | void 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 | |
1031 | void KSMServer::wmChanged() |
1032 | { |
1033 | KGlobal::config()->reparseConfiguration(); |
1034 | selectWm( "" ); |
1035 | } |
1036 | |
1037 | void 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 | |
1064 | void KSMServer::defaultLogout() |
1065 | { |
1066 | shutdown(KWorkSpace::ShutdownConfirmYes, KWorkSpace::ShutdownTypeDefault, KWorkSpace::ShutdownModeDefault); |
1067 | } |
1068 | |
1069 | void KSMServer::logoutWithoutConfirmation() |
1070 | { |
1071 | shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeNone, KWorkSpace::ShutdownModeDefault); |
1072 | } |
1073 | |
1074 | void KSMServer::haltWithoutConfirmation() |
1075 | { |
1076 | shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeHalt, KWorkSpace::ShutdownModeDefault); |
1077 | } |
1078 | |
1079 | void KSMServer::rebootWithoutConfirmation() |
1080 | { |
1081 | shutdown(KWorkSpace::ShutdownConfirmNo, KWorkSpace::ShutdownTypeReboot, KWorkSpace::ShutdownModeDefault); |
1082 | } |
1083 | |