Changeset View
Standalone View
sm.cpp
Show All 23 Lines | |||||
24 | #include <unistd.h> | 24 | #include <unistd.h> | ||
25 | #include <cstdlib> | 25 | #include <cstdlib> | ||
26 | #include <pwd.h> | 26 | #include <pwd.h> | ||
27 | #include <kconfig.h> | 27 | #include <kconfig.h> | ||
28 | 28 | | |||
29 | #include "workspace.h" | 29 | #include "workspace.h" | ||
30 | #include "x11client.h" | 30 | #include "x11client.h" | ||
31 | #include <QDebug> | 31 | #include <QDebug> | ||
32 | #include <QFile> | | |||
33 | #include <QSocketNotifier> | | |||
34 | #include <QSessionManager> | 32 | #include <QSessionManager> | ||
35 | 33 | | |||
34 | #include <QDBusConnection> | ||||
35 | #include "sessionadaptor.h" | ||||
36 | | ||||
36 | namespace KWin | 37 | namespace KWin | ||
37 | { | 38 | { | ||
38 | 39 | | |||
39 | static bool gs_sessionManagerIsKSMServer = false; | | |||
40 | static KConfig *sessionConfig(QString id, QString key) | 40 | static KConfig *sessionConfig(QString id, QString key) | ||
41 | { | 41 | { | ||
42 | static KConfig *config = nullptr; | 42 | static KConfig *config = nullptr; | ||
43 | static QString lastId; | 43 | static QString lastId; | ||
44 | static QString lastKey; | 44 | static QString lastKey; | ||
45 | static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); | 45 | static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); | ||
46 | if (id != lastId || key != lastKey) { | 46 | if (id != lastId || key != lastKey) { | ||
47 | delete config; | 47 | delete config; | ||
Show All 30 Lines | 76 | for (int i = NET::Unknown; | |||
78 | ++i) | 78 | ++i) | ||
79 | if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1 | 79 | if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1 | ||
80 | return static_cast< NET::WindowType >(i); | 80 | return static_cast< NET::WindowType >(i); | ||
81 | return static_cast< NET::WindowType >(-2); // undefined | 81 | return static_cast< NET::WindowType >(-2); // undefined | ||
82 | } | 82 | } | ||
83 | 83 | | |||
84 | void Workspace::saveState(QSessionManager &sm) | 84 | void Workspace::saveState(QSessionManager &sm) | ||
85 | { | 85 | { | ||
86 | bool sessionManagerIsKSMServer = QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.ksmserver").value(); | ||||
87 | | ||||
86 | // If the session manager is ksmserver, save stacking | 88 | // If the session manager is ksmserver, save stacking | ||
87 | // order, active window, active desktop etc. in phase 1, | 89 | // order, active window, active desktop etc. in phase 1, | ||
88 | // as ksmserver assures no interaction will be done | 90 | // as ksmserver assures no interaction will be done | ||
89 | // before the WM finishes phase 1. Saving in phase 2 is | 91 | // before the WM finishes phase 1. Saving in phase 2 is | ||
90 | // too late, as possible user interaction may change some things. | 92 | // too late, as possible user interaction may change some things. | ||
91 | // Phase2 is still needed though (ICCCM 5.2) | 93 | // Phase2 is still needed though (ICCCM 5.2) | ||
92 | KConfig *config = sessionConfig(sm.sessionId(), sm.sessionKey()); | 94 | KConfig *config = sessionConfig(sm.sessionId(), sm.sessionKey()); | ||
93 | if (!sm.isPhase2()) { | 95 | if (!sm.isPhase2()) { | ||
94 | KConfigGroup cg(config, "Session"); | 96 | KConfigGroup cg(config, "Session"); | ||
95 | cg.writeEntry("AllowsInteraction", sm.allowsInteraction()); | 97 | cg.writeEntry("AllowsInteraction", sm.allowsInteraction()); | ||
96 | sessionSaveStarted(); | 98 | if (sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it | ||
zzag: Hmm, we no longer enter this path since gs_sessionManagerIsKSMServer is always false. | |||||
That is true right now, but I have intentions to port save and half porting part of that now only complicates things - especially if we do keep an XSM path. davidedmundson: That is true right now, but I have intentions to port save and half porting part of that now… | |||||
I'll add a gs_sessionManagerIsKSMServer = getenv(session) == "plasma" That way we don't even have a micro regression if I don't finish the rest before next release. davidedmundson: I'll add a
gs_sessionManagerIsKSMServer = getenv(session) == "plasma"
That way we don't even… | |||||
97 | if (gs_sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it | | |||
98 | storeSession(config, SMSavePhase0); | 99 | storeSession(config, SMSavePhase0); | ||
99 | config->markAsClean(); // don't write Phase #1 data to disk | 100 | config->markAsClean(); // don't write Phase #1 data to disk | ||
100 | sm.release(); // Qt doesn't automatically release in this case (bug?) | 101 | sm.release(); // Qt doesn't automatically release in this case (bug?) | ||
101 | sm.requestPhase2(); | 102 | sm.requestPhase2(); | ||
102 | return; | 103 | return; | ||
103 | } | 104 | } | ||
104 | storeSession(config, gs_sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); | 105 | storeSession(config, sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); | ||
105 | config->sync(); | 106 | config->sync(); | ||
106 | 107 | | |||
107 | // inform the smserver on how to clean-up after us | 108 | // inform the smserver on how to clean-up after us | ||
108 | const QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); | 109 | const QString localFilePath = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1Char('/') + config->name(); | ||
109 | if (QFile::exists(localFilePath)) { // expectable for the sync | 110 | if (QFile::exists(localFilePath)) { // expectable for the sync | ||
110 | sm.setDiscardCommand(QStringList() << QStringLiteral("rm") << localFilePath); | 111 | sm.setDiscardCommand(QStringList() << QStringLiteral("rm") << localFilePath); | ||
111 | } | 112 | } | ||
112 | } | 113 | } | ||
113 | 114 | | |||
114 | // I bet this is broken, just like everywhere else in KDE | | |||
115 | void Workspace::commitData(QSessionManager &sm) | | |||
116 | { | | |||
117 | if (!sm.isPhase2()) | | |||
118 | sessionSaveStarted(); | | |||
119 | } | | |||
120 | | ||||
121 | // Workspace | 115 | // Workspace | ||
122 | 116 | | |||
123 | /** | 117 | /** | ||
124 | * Stores the current session in the config file | 118 | * Stores the current session in the config file | ||
125 | * | 119 | * | ||
126 | * @see loadSessionInfo | 120 | * @see loadSessionInfo | ||
127 | */ | 121 | */ | ||
128 | void Workspace::storeSession(KConfig* config, SMSavePhase phase) | 122 | void Workspace::storeSession(KConfig* config, SMSavePhase phase) | ||
▲ Show 20 Lines • Show All 227 Lines • ▼ Show 20 Line(s) | 348 | if (wmCommand.isEmpty() || info->wmCommand == wmCommand) { | |||
356 | session.removeAll(info); | 350 | session.removeAll(info); | ||
357 | } | 351 | } | ||
358 | } | 352 | } | ||
359 | } | 353 | } | ||
360 | } | 354 | } | ||
361 | return realInfo; | 355 | return realInfo; | ||
362 | } | 356 | } | ||
363 | 357 | | |||
364 | // KWin's focus stealing prevention causes problems with user interaction | 358 | SessionManager::SessionManager(QObject *parent) | ||
365 | // during session save, as it prevents possible dialogs from getting focus. | 359 | : QObject(parent) | ||
366 | // Therefore it's temporarily disabled during session saving. Start of | | |||
367 | // session saving can be detected in SessionManager::saveState() above, | | |||
368 | // but Qt doesn't have API for saying when session saved finished (either | | |||
369 | // successfully, or was canceled). Therefore, create another connection | | |||
370 | // to session manager, that will provide this information. | | |||
371 | // Similarly the remember feature of window-specific settings should be disabled | | |||
372 | // during KDE shutdown when windows may move e.g. because of Kicker going away | | |||
373 | // (struts changing). When session saving starts, it can be cancelled, in which | | |||
374 | // case the shutdown_cancelled callback is invoked, or it's a checkpoint that | | |||
375 | // is immediatelly followed by save_complete, or finally it's a shutdown that | | |||
376 | // is immediatelly followed by die callback. So getting save_yourself with shutdown | | |||
377 | // set disables window-specific settings remembering, getting shutdown_cancelled | | |||
378 | // re-enables, otherwise KWin will go away after die. | | |||
379 | static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool) | | |||
380 | { | 360 | { | ||
381 | SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); | 361 | new SessionAdaptor(this); | ||
382 | if (conn_P != session->connection()) | 362 | QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this); | ||
383 | return; | | |||
384 | if (shutdown) | | |||
385 | RuleBook::self()->setUpdatesDisabled(true); | | |||
386 | SmcSaveYourselfDone(conn_P, True); | | |||
387 | } | 363 | } | ||
388 | 364 | | |||
389 | static void die(SmcConn conn_P, SmPointer ptr) | 365 | SessionManager::~SessionManager() | ||
390 | { | 366 | { | ||
zzag: Style: Add whitespace between colon and QObject | |||||
391 | SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); | | |||
392 | if (conn_P != session->connection()) | | |||
393 | return; | | |||
394 | // session->saveDone(); we will quit anyway | | |||
395 | session->close(); | | |||
396 | } | 367 | } | ||
397 | 368 | | |||
398 | static void save_complete(SmcConn conn_P, SmPointer ptr) | 369 | SessionState SessionManager::state() const | ||
399 | { | 370 | { | ||
400 | SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); | 371 | return m_sessionState; | ||
401 | if (conn_P != session->connection()) | | |||
402 | return; | | |||
403 | session->saveDone(); | | |||
404 | } | 372 | } | ||
zzag: Use QStringLiteral please. | |||||
405 | 373 | | |||
406 | static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr) | 374 | void SessionManager::setState(uint state) | ||
407 | { | 375 | { | ||
408 | SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); | 376 | switch (state) { | ||
409 | if (conn_P != session->connection()) | 377 | case static_cast<uint>(SessionState::Saving): | ||
378 | setState(SessionState::Saving); | ||||
379 | break; | ||||
380 | case static_cast<uint>(SessionState::Quitting): | ||||
381 | setState(SessionState::Quitting); | ||||
382 | break; | ||||
383 | default: | ||||
384 | setState(SessionState::Normal); | ||||
Style: Case labels should not be indented. Also, I'd prefer to be more explicit about the conversion between "d-bus enums" and our enums, i.e. use integer values in the case labels. Another way to implement this method without encoding "d-bus enum" values in our types is to use a static QVector and value() method. I don't have a strong opinion though. It's up to you whether to leave this method as is. However, please fix the coding style issue. zzag: Style: Case labels should not be indented.
---
Also, I'd prefer to be more explicit about the… | |||||
zzag: s/integer values/integer literals/ | |||||
385 | } | ||||
386 | } | ||||
387 | | ||||
388 | void SessionManager::setState(SessionState state) | ||||
389 | { | ||||
390 | if (state == m_sessionState) { | ||||
410 | return; | 391 | return; | ||
411 | RuleBook::self()->setUpdatesDisabled(false); // re-enable | | |||
412 | // no need to differentiate between successful finish and cancel | | |||
413 | session->saveDone(); | | |||
414 | } | | |||
415 | | ||||
416 | void SessionSaveDoneHelper::saveDone() | | |||
417 | { | | |||
418 | if (Workspace::self()) | | |||
419 | Workspace::self()->sessionSaveDone(); | | |||
420 | } | | |||
421 | | ||||
422 | SessionSaveDoneHelper::SessionSaveDoneHelper() | | |||
423 | { | | |||
424 | SmcCallbacks calls; | | |||
425 | calls.save_yourself.callback = save_yourself; | | |||
426 | calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this); | | |||
427 | calls.die.callback = die; | | |||
428 | calls.die.client_data = reinterpret_cast< SmPointer >(this); | | |||
429 | calls.save_complete.callback = save_complete; | | |||
430 | calls.save_complete.client_data = reinterpret_cast< SmPointer >(this); | | |||
431 | calls.shutdown_cancelled.callback = shutdown_cancelled; | | |||
432 | calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this); | | |||
433 | char* id = nullptr; | | |||
434 | char err[ 11 ]; | | |||
435 | conn = SmcOpenConnection(nullptr, nullptr, 1, 0, | | |||
436 | SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | | |||
437 | | SmcShutdownCancelledProcMask, &calls, nullptr, &id, 10, err); | | |||
438 | if (id != nullptr) | | |||
439 | free(id); | | |||
440 | if (conn == nullptr) | | |||
441 | return; // no SM | | |||
442 | | ||||
443 | // detect ksmserver | | |||
444 | char* vendor = SmcVendor(conn); | | |||
445 | gs_sessionManagerIsKSMServer = qstrcmp(vendor, "KDE") == 0; | | |||
446 | free(vendor); | | |||
447 | | ||||
448 | // set the required properties, mostly dummy values | | |||
449 | SmPropValue propvalue[ 5 ]; | | |||
450 | SmProp props[ 5 ]; | | |||
451 | propvalue[ 0 ].length = sizeof(unsigned char); | | |||
452 | unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere | | |||
453 | propvalue[ 0 ].value = &value0; | | |||
454 | props[ 0 ].name = const_cast< char* >(SmRestartStyleHint); | | |||
455 | props[ 0 ].type = const_cast< char* >(SmCARD8); | | |||
456 | props[ 0 ].num_vals = 1; | | |||
457 | props[ 0 ].vals = &propvalue[ 0 ]; | | |||
458 | struct passwd* entry = getpwuid(geteuid()); | | |||
459 | propvalue[ 1 ].length = entry != nullptr ? strlen(entry->pw_name) : 0; | | |||
460 | propvalue[ 1 ].value = (SmPointer)(entry != nullptr ? entry->pw_name : ""); | | |||
461 | props[ 1 ].name = const_cast< char* >(SmUserID); | | |||
462 | props[ 1 ].type = const_cast< char* >(SmARRAY8); | | |||
463 | props[ 1 ].num_vals = 1; | | |||
464 | props[ 1 ].vals = &propvalue[ 1 ]; | | |||
465 | propvalue[ 2 ].length = 0; | | |||
466 | propvalue[ 2 ].value = (SmPointer)(""); | | |||
467 | props[ 2 ].name = const_cast< char* >(SmRestartCommand); | | |||
468 | props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8); | | |||
469 | props[ 2 ].num_vals = 1; | | |||
470 | props[ 2 ].vals = &propvalue[ 2 ]; | | |||
471 | propvalue[ 3 ].length = strlen("kwinsmhelper"); | | |||
472 | propvalue[ 3 ].value = (SmPointer)"kwinsmhelper"; | | |||
473 | props[ 3 ].name = const_cast< char* >(SmProgram); | | |||
474 | props[ 3 ].type = const_cast< char* >(SmARRAY8); | | |||
475 | props[ 3 ].num_vals = 1; | | |||
476 | props[ 3 ].vals = &propvalue[ 3 ]; | | |||
477 | propvalue[ 4 ].length = 0; | | |||
478 | propvalue[ 4 ].value = (SmPointer)(""); | | |||
479 | props[ 4 ].name = const_cast< char* >(SmCloneCommand); | | |||
480 | props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8); | | |||
481 | props[ 4 ].num_vals = 1; | | |||
482 | props[ 4 ].vals = &propvalue[ 4 ]; | | |||
483 | SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] }; | | |||
484 | SmcSetProperties(conn, 5, p); | | |||
485 | notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)), | | |||
486 | QSocketNotifier::Read, this); | | |||
487 | connect(notifier, SIGNAL(activated(int)), SLOT(processData())); | | |||
488 | } | | |||
489 | | ||||
490 | SessionSaveDoneHelper::~SessionSaveDoneHelper() | | |||
491 | { | | |||
492 | close(); | | |||
493 | } | | |||
494 | | ||||
495 | void SessionSaveDoneHelper::close() | | |||
496 | { | | |||
497 | if (conn != nullptr) { | | |||
498 | delete notifier; | | |||
499 | SmcCloseConnection(conn, 0, nullptr); | | |||
500 | } | | |||
501 | conn = nullptr; | | |||
502 | } | | |||
503 | | ||||
504 | void SessionSaveDoneHelper::processData() | | |||
505 | { | | |||
506 | if (conn != nullptr) | | |||
507 | IceProcessMessages(SmcGetIceConnection(conn), nullptr, nullptr); | | |||
508 | } | | |||
509 | | ||||
510 | void Workspace::sessionSaveDone() | | |||
511 | { | | |||
512 | session_saving = false; | | |||
513 | foreach (X11Client *c, clients) { | | |||
514 | c->setSessionActivityOverride(false); | | |||
515 | } | 392 | } | ||
393 | // If we're starting to save a session | ||||
394 | if (state == SessionState::Saving) { | ||||
395 | RuleBook::self()->setUpdatesDisabled(true); | ||||
396 | } | ||||
397 | // If we're ending a save session due to either completion or cancellation | ||||
398 | if (m_sessionState == SessionState::Saving) { | ||||
399 | RuleBook::self()->setUpdatesDisabled(false); | ||||
400 | Workspace::self()->forEachClient([](X11Client *client) { | ||||
401 | client->setSessionActivityOverride(false); | ||||
402 | }); | ||||
403 | } | ||||
404 | m_sessionState = state; | ||||
405 | emit stateChanged(); | ||||
zzag: Wrap raw c strings in QStringLiteral. | |||||
516 | } | 406 | } | ||
517 | 407 | | |||
518 | } // namespace | 408 | } // namespace | ||
519 | 409 | |
Hmm, we no longer enter this path since gs_sessionManagerIsKSMServer is always false.