diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -548,6 +548,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 (sessionSaving() && 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 (sessionSaving() && level <= 2) { // <= normal return true; } AbstractClient* ac = mostRecentlyActivatedClient(); 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/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,10 @@ + + + + + + + + + diff --git a/sm.h b/sm.h --- a/sm.h +++ b/sm.h @@ -28,9 +28,6 @@ #include #include -#include -#include - class QSocketNotifier; namespace KWin @@ -77,23 +74,14 @@ SMSavePhase2Full // complete saving in phase2, there was no phase 0 }; -class KWIN_EXPORT SessionSaveDoneHelper - : public QObject +class SessionManager: 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; + SessionManager(QObject *parent); + ~SessionManager(); +public Q_SLOTS: + void setState(const QString &state); }; } // namespace diff --git a/sm.cpp b/sm.cpp --- a/sm.cpp +++ b/sm.cpp @@ -29,10 +29,11 @@ #include "workspace.h" #include "x11client.h" #include -#include -#include #include +#include +#include "sessionadaptor.h" + namespace KWin { @@ -93,7 +94,6 @@ 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 storeSession(config, SMSavePhase0); config->markAsClean(); // don't write Phase #1 data to disk @@ -111,13 +111,6 @@ } } -// I bet this is broken, just like everywhere else in KDE -void Workspace::commitData(QSessionManager &sm) -{ - if (!sm.isPhase2()) - sessionSaveStarted(); -} - // Workspace /** @@ -361,158 +354,24 @@ 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) -{ - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - if (shutdown) - RuleBook::self()->setUpdatesDisabled(true); - SmcSaveYourselfDone(conn_P, True); -} - -static void die(SmcConn conn_P, SmPointer ptr) +SessionManager::SessionManager(QObject *parent) + :QObject(parent) { - 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) -{ - SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr); - if (conn_P != session->connection()) - return; - session->saveDone(); + new SessionAdaptor(this); + QDBusConnection::sessionBus().registerObject("/Session", this); } -static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr) +SessionManager::~SessionManager() { - 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() +void SessionManager::setState(const QString &state) { - 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); - } - conn = nullptr; -} - -void SessionSaveDoneHelper::processData() -{ - if (conn != nullptr) - IceProcessMessages(SmcGetIceConnection(conn), nullptr, nullptr); -} - -void Workspace::sessionSaveDone() -{ - session_saving = false; - foreach (X11Client *c, clients) { - c->setSessionActivityOverride(false); - } + static QHash s_stateMap = { + {"saving", SessionState::Saving}, + {"quitting", SessionState::Quitting} + }; + Workspace::self()->setSessionState(s_stateMap.value(state, SessionState::Normal)); } } // namespace diff --git a/workspace.h b/workspace.h --- a/workspace.h +++ b/workspace.h @@ -339,8 +339,6 @@ bool globalShortcutsDisabled() const; void disableGlobalShortcutsForClient(bool disable); - void sessionSaveStarted(); - void sessionSaveDone(); void setWasUserInteraction(); bool wasUserInteraction() const; bool sessionSaving() const; @@ -479,6 +477,13 @@ void updateClientArea(); + /** + * Updates kwin's internal knowledge of the state of the + * session outside. + */ + void setSessionState(SessionState state); + SessionState sessionState() const; + private Q_SLOTS: void desktopResized(); void selectWmInputEventMask(); @@ -492,7 +497,6 @@ // session management void saveState(QSessionManager &sm); - void commitData(QSessionManager &sm); Q_SIGNALS: /** @@ -531,6 +535,8 @@ */ void internalClientRemoved(KWin::InternalClient *client); + void sessionStateChanged(); + private: void init(); void initWithX11(); @@ -616,7 +622,8 @@ bool was_user_interaction; QScopedPointer m_wasUserInteractionFilter; - bool session_saving; + + SessionState m_sessionState = SessionState::Normal; int session_active_client; int session_desktop; @@ -742,14 +749,9 @@ return was_user_interaction; } -inline void Workspace::sessionSaveStarted() -{ - session_saving = true; -} - inline bool Workspace::sessionSaving() const { - return session_saving; + return m_sessionState == SessionState::Saving; } 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) @@ -156,7 +155,6 @@ if (!sessionKey.isEmpty()) loadSessionInfo(sessionKey); - connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData); connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState); RuleBook::create(this)->load(); @@ -189,6 +187,7 @@ connect(this, &Workspace::configChanged, decorationBridge, &Decoration::DecorationBridge::reconfigure); new DBusInterface(this); + new SessionManager(this); Outline::create(this); @@ -1827,4 +1826,29 @@ emit internalClientRemoved(client); } +void Workspace::setSessionState(SessionState state) +{ + if (state == m_sessionState) { + return; + } + //starting session save + if (state == SessionState::Saving) { + RuleBook::self()->setUpdatesDisabled(true); + } + //Session save is done + if (m_sessionState == SessionState::Saving) { + foreach (X11Client *c, clients) { + RuleBook::self()->setUpdatesDisabled(false); + c->setSessionActivityOverride(false); + } + } + m_sessionState = state; + emit sessionStateChanged(); +} + +SessionState Workspace::sessionState() const +{ + return m_sessionState; +} + } // namespace