diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -549,6 +549,7 @@ qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS ${kwin_effects_dbus_xml} effects.h KWin::EffectsHandlerImpl) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.OrientationSensor.xml orientation_sensor.h KWin::OrientationSensor) qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.VirtualDesktopManager.xml dbusinterface.h KWin::VirtualDesktopManagerDBusInterface) +qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.KWin.Session.xml sm.h KWin::SessionManager) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/kf5_org.freedesktop.ScreenSaver.xml screenlocker_interface) qt5_add_dbus_interface(kwin_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface) diff --git a/activation.cpp b/activation.cpp --- a/activation.cpp +++ b/activation.cpp @@ -558,7 +558,7 @@ if (time == -1U) time = c->userTime(); int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); - if (session_saving && level <= FSP::Medium) { // <= normal + if (sessionManager()->state() == SessionState::Saving && level <= FSP::Medium) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); @@ -635,7 +635,7 @@ bool Workspace::allowFullClientRaising(const KWin::AbstractClient *c, xcb_timestamp_t time) { int level = c->rules()->checkFSP(options->focusStealingPreventionLevel()); - if (session_saving && level <= 2) { // <= normal + if (sessionManager()->state() == SessionState::Saving && level <= 2) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); diff --git a/activities.cpp b/activities.cpp --- a/activities.cpp +++ b/activities.cpp @@ -121,7 +121,7 @@ bool Activities::start(const QString &id) { Workspace *ws = Workspace::self(); - if (ws->sessionSaving()) { + if (ws->sessionManager()->state() == SessionState::Saving) { return false; //ksmserver doesn't queue requests (yet) } @@ -143,7 +143,7 @@ bool Activities::stop(const QString &id) { - if (Workspace::self()->sessionSaving()) { + if (Workspace::self()->sessionManager()->state() == SessionState::Saving) { return false; //ksmserver doesn't queue requests (yet) //FIXME what about session *loading*? } @@ -157,7 +157,7 @@ void Activities::reallyStop(const QString &id) { Workspace *ws = Workspace::self(); - if (ws->sessionSaving()) + if (ws->sessionManager()->state() == SessionState::Saving) return; //ksmserver doesn't queue requests (yet) qCDebug(KWIN_CORE) << id; diff --git a/libkwineffects/kwinglobals.h b/libkwineffects/kwinglobals.h --- a/libkwineffects/kwinglobals.h +++ b/libkwineffects/kwinglobals.h @@ -138,6 +138,16 @@ Right }; +/** + * Represents the state of the session running outside kwin + * Under Plasma this is managed by ksmserver + */ +enum class SessionState { + Normal, + Saving, + Quitting +}; + inline KWIN_EXPORT xcb_connection_t *connection() { diff --git a/main_x11.cpp b/main_x11.cpp --- a/main_x11.cpp +++ b/main_x11.cpp @@ -480,9 +480,6 @@ a.start(); - KWin::SessionSaveDoneHelper helper; - Q_UNUSED(helper); // The sessionsavedonehelper opens a side channel to the smserver, - // listens for events and talks to it, so it needs to be created. return a.exec(); } diff --git a/manage.cpp b/manage.cpp --- a/manage.cpp +++ b/manage.cpp @@ -432,7 +432,8 @@ init_minimize = false; // SELI TODO: Even e.g. for NET::Utility? } // If a dialog is shown for minimized window, minimize it too - if (!init_minimize && isTransient() && mainClients().count() > 0 && !workspace()->sessionSaving()) { + if (!init_minimize && isTransient() && mainClients().count() > 0 && + workspace()->sessionManager()->state() != SessionState::Saving) { bool visible_parent = false; // Use allMainClients(), to include also main clients of group transients // that have been optimized out in X11Client::checkGroupTransients() @@ -532,13 +533,15 @@ else allow = workspace()->allowClientActivation(this, userTime(), false); + const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving; + // If session saving, force showing new windows (i.e. "save file?" dialogs etc.) // also force if activation is allowed - if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || workspace()->sessionSaving() )) + if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving )) VirtualDesktopManager::self()->setCurrent( desktop()); // If the window is on an inactive activity during session saving, temporarily force it to show. - if( !isMapped && !session && workspace()->sessionSaving() && !isOnCurrentActivity()) { + if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) { setSessionActivityOverride( true ); foreach( AbstractClient* c, mainClients()) { if (X11Client *mc = dynamic_cast(c)) { diff --git a/org.kde.KWin.Session.xml b/org.kde.KWin.Session.xml new file mode 100644 --- /dev/null +++ b/org.kde.KWin.Session.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/sm.h b/sm.h --- a/sm.h +++ b/sm.h @@ -28,16 +28,31 @@ #include #include -#include -#include - -class QSocketNotifier; - namespace KWin { class X11Client; +class SessionManager : public QObject +{ + Q_OBJECT +public: + SessionManager(QObject *parent); + ~SessionManager() override; + + SessionState state() const; + +Q_SIGNALS: + void stateChanged(); + +public Q_SLOTS: // DBus API + void setState(uint state); + +private: + void setState(SessionState state); + SessionState m_sessionState; +}; + struct SessionInfo { QByteArray sessionId; QByteArray windowRole; @@ -77,25 +92,6 @@ SMSavePhase2Full // complete saving in phase2, there was no phase 0 }; -class KWIN_EXPORT SessionSaveDoneHelper - : public QObject -{ - Q_OBJECT -public: - SessionSaveDoneHelper(); - ~SessionSaveDoneHelper() override; - SmcConn connection() const { - return conn; - } - void saveDone(); - void close(); -private Q_SLOTS: - void processData(); -private: - QSocketNotifier* notifier; - SmcConn conn; -}; - } // namespace #endif diff --git a/sm.cpp b/sm.cpp --- a/sm.cpp +++ b/sm.cpp @@ -29,14 +29,14 @@ #include "workspace.h" #include "x11client.h" #include -#include -#include #include +#include +#include "sessionadaptor.h" + namespace KWin { -static bool gs_sessionManagerIsKSMServer = false; static KConfig *sessionConfig(QString id, QString key) { static KConfig *config = nullptr; @@ -83,6 +83,8 @@ void Workspace::saveState(QSessionManager &sm) { + bool sessionManagerIsKSMServer = QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.ksmserver").value(); + // If the session manager is ksmserver, save stacking // order, active window, active desktop etc. in phase 1, // as ksmserver assures no interaction will be done @@ -93,15 +95,14 @@ if (!sm.isPhase2()) { KConfigGroup cg(config, "Session"); cg.writeEntry("AllowsInteraction", sm.allowsInteraction()); - sessionSaveStarted(); - if (gs_sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it + if (sessionManagerIsKSMServer) // save stacking order etc. before "save file?" etc. dialogs change it storeSession(config, SMSavePhase0); config->markAsClean(); // don't write Phase #1 data to disk sm.release(); // Qt doesn't automatically release in this case (bug?) sm.requestPhase2(); return; } - storeSession(config, gs_sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); + storeSession(config, sessionManagerIsKSMServer ? SMSavePhase2 : SMSavePhase2Full); config->sync(); // inform the smserver on how to clean-up after us @@ -111,13 +112,6 @@ } } -// I bet this is broken, just like everywhere else in KDE -void Workspace::commitData(QSessionManager &sm) -{ - if (!sm.isPhase2()) - sessionSaveStarted(); -} - // Workspace /** @@ -361,158 +355,54 @@ return realInfo; } -// KWin's focus stealing prevention causes problems with user interaction -// during session save, as it prevents possible dialogs from getting focus. -// Therefore it's temporarily disabled during session saving. Start of -// session saving can be detected in SessionManager::saveState() above, -// but Qt doesn't have API for saying when session saved finished (either -// successfully, or was canceled). Therefore, create another connection -// to session manager, that will provide this information. -// Similarly the remember feature of window-specific settings should be disabled -// during KDE shutdown when windows may move e.g. because of Kicker going away -// (struts changing). When session saving starts, it can be cancelled, in which -// case the shutdown_cancelled callback is invoked, or it's a checkpoint that -// is immediatelly followed by save_complete, or finally it's a shutdown that -// is immediatelly followed by die callback. So getting save_yourself with shutdown -// set disables window-specific settings remembering, getting shutdown_cancelled -// re-enables, otherwise KWin will go away after die. -static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool) +SessionManager::SessionManager(QObject *parent) + : QObject(parent) { - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - if (shutdown) - RuleBook::self()->setUpdatesDisabled(true); - SmcSaveYourselfDone(conn_P, True); + new SessionAdaptor(this); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this); } -static void die(SmcConn conn_P, SmPointer ptr) +SessionManager::~SessionManager() { - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - // session->saveDone(); we will quit anyway - session->close(); } -static void save_complete(SmcConn conn_P, SmPointer ptr) +SessionState SessionManager::state() const { - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - session->saveDone(); + return m_sessionState; } -static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr) +void SessionManager::setState(uint state) { - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - RuleBook::self()->setUpdatesDisabled(false); // re-enable - // no need to differentiate between successful finish and cancel - session->saveDone(); -} - -void SessionSaveDoneHelper::saveDone() -{ - if (Workspace::self()) - Workspace::self()->sessionSaveDone(); -} - -SessionSaveDoneHelper::SessionSaveDoneHelper() -{ - SmcCallbacks calls; - calls.save_yourself.callback = save_yourself; - calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this); - calls.die.callback = die; - calls.die.client_data = reinterpret_cast< SmPointer >(this); - calls.save_complete.callback = save_complete; - calls.save_complete.client_data = reinterpret_cast< SmPointer >(this); - calls.shutdown_cancelled.callback = shutdown_cancelled; - calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this); - char* id = nullptr; - char err[ 11 ]; - conn = SmcOpenConnection(nullptr, nullptr, 1, 0, - SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask - | SmcShutdownCancelledProcMask, &calls, nullptr, &id, 10, err); - if (id != nullptr) - free(id); - if (conn == nullptr) - return; // no SM - - // detect ksmserver - char* vendor = SmcVendor(conn); - gs_sessionManagerIsKSMServer = qstrcmp(vendor, "KDE") == 0; - free(vendor); - - // set the required properties, mostly dummy values - SmPropValue propvalue[ 5 ]; - SmProp props[ 5 ]; - propvalue[ 0 ].length = sizeof(unsigned char); - unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere - propvalue[ 0 ].value = &value0; - props[ 0 ].name = const_cast< char* >(SmRestartStyleHint); - props[ 0 ].type = const_cast< char* >(SmCARD8); - props[ 0 ].num_vals = 1; - props[ 0 ].vals = &propvalue[ 0 ]; - struct passwd* entry = getpwuid(geteuid()); - propvalue[ 1 ].length = entry != nullptr ? strlen(entry->pw_name) : 0; - propvalue[ 1 ].value = (SmPointer)(entry != nullptr ? entry->pw_name : ""); - props[ 1 ].name = const_cast< char* >(SmUserID); - props[ 1 ].type = const_cast< char* >(SmARRAY8); - props[ 1 ].num_vals = 1; - props[ 1 ].vals = &propvalue[ 1 ]; - propvalue[ 2 ].length = 0; - propvalue[ 2 ].value = (SmPointer)(""); - props[ 2 ].name = const_cast< char* >(SmRestartCommand); - props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8); - props[ 2 ].num_vals = 1; - props[ 2 ].vals = &propvalue[ 2 ]; - propvalue[ 3 ].length = strlen("kwinsmhelper"); - propvalue[ 3 ].value = (SmPointer)"kwinsmhelper"; - props[ 3 ].name = const_cast< char* >(SmProgram); - props[ 3 ].type = const_cast< char* >(SmARRAY8); - props[ 3 ].num_vals = 1; - props[ 3 ].vals = &propvalue[ 3 ]; - propvalue[ 4 ].length = 0; - propvalue[ 4 ].value = (SmPointer)(""); - props[ 4 ].name = const_cast< char* >(SmCloneCommand); - props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8); - props[ 4 ].num_vals = 1; - props[ 4 ].vals = &propvalue[ 4 ]; - SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] }; - SmcSetProperties(conn, 5, p); - notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)), - QSocketNotifier::Read, this); - connect(notifier, SIGNAL(activated(int)), SLOT(processData())); -} - -SessionSaveDoneHelper::~SessionSaveDoneHelper() -{ - close(); -} - -void SessionSaveDoneHelper::close() -{ - if (conn != nullptr) { - delete notifier; - SmcCloseConnection(conn, 0, nullptr); + switch (state) { + case 0: + setState(SessionState::Saving); + break; + case 1: + setState(SessionState::Quitting); + break; + default: + setState(SessionState::Normal); } - conn = nullptr; } -void SessionSaveDoneHelper::processData() +void SessionManager::setState(SessionState state) { - if (conn != nullptr) - IceProcessMessages(SmcGetIceConnection(conn), nullptr, nullptr); -} - -void Workspace::sessionSaveDone() -{ - session_saving = false; - foreach (X11Client *c, clients) { - c->setSessionActivityOverride(false); + if (state == m_sessionState) { + return; + } + // If we're starting to save a session + if (state == SessionState::Saving) { + RuleBook::self()->setUpdatesDisabled(true); + } + // If we're ending a save session due to either completion or cancellation + if (m_sessionState == SessionState::Saving) { + RuleBook::self()->setUpdatesDisabled(false); + Workspace::self()->forEachClient([](X11Client *client) { + client->setSessionActivityOverride(false); + }); } + m_sessionState = state; + emit stateChanged(); } } // namespace diff --git a/workspace.h b/workspace.h --- a/workspace.h +++ b/workspace.h @@ -244,6 +244,8 @@ void stackScreenEdgesUnderOverrideRedirect(); + SessionManager *sessionManager() const; + public: QPoint cascadeOffset(const AbstractClient *c) const; @@ -339,11 +341,8 @@ bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); - void sessionSaveStarted(); - void sessionSaveDone(); void setWasUserInteraction(); bool wasUserInteraction() const; - bool sessionSaving() const; int packPositionLeft(const AbstractClient *client, int oldX, bool leftEdge) const; int packPositionRight(const AbstractClient *client, int oldX, bool rightEdge) const; @@ -492,7 +491,6 @@ // session management void saveState(QSessionManager &sm); - void commitData(QSessionManager &sm); Q_SIGNALS: /** @@ -616,7 +614,7 @@ bool was_user_interaction; QScopedPointer m_wasUserInteractionFilter; - bool session_saving; + int session_active_client; int session_desktop; @@ -666,6 +664,7 @@ QList m_genericEventFilters; QScopedPointer m_movingClientFilter; + SessionManager *m_sessionManager; private: friend bool performTransiencyCheck(); friend Workspace *workspace(); @@ -742,14 +741,9 @@ return was_user_interaction; } -inline void Workspace::sessionSaveStarted() -{ - session_saving = true; -} - -inline bool Workspace::sessionSaving() const +inline SessionManager *Workspace::sessionManager() const { - return session_saving; + return m_sessionManager; } inline bool Workspace::showingDesktop() const diff --git a/workspace.cpp b/workspace.cpp --- a/workspace.cpp +++ b/workspace.cpp @@ -118,7 +118,6 @@ , force_restacking(false) , showing_desktop(false) , was_user_interaction(false) - , session_saving(false) , block_focus(0) , m_userActionsMenu(new UserActionsMenu(this)) , client_keys_dialog(nullptr) @@ -128,6 +127,7 @@ , startup(nullptr) , set_active_client_recursion(0) , block_stacking_updates(0) + , m_sessionManager(new SessionManager(this)) { // If KWin was already running it saved its configuration after loosing the selection -> Reread QFuture reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration); @@ -156,7 +156,6 @@ if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); - connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load();