Commit ec610fd7 authored by David Edmundson's avatar David Edmundson
Browse files

Port one of session management connections state to a custom API

Summary:
Currently kwin opens a second ICE connection to ksmserver in order to
tell the state of kwin's whether we're logging out and saving clients or
not.

This requires that kwin launches after ksmserver to have the connection
which is a dependency I want to break.

Practically this code is already ksmserver specific as it relies on some
custom code that sends the first saveState request to kwin first.

Instead we can replace it with a bespoke IPC over DBus and siplify the
code both end. This will allow several other future enhancements that we
want with regards to handling the session state, as well as make an
effort platform agnostic session management, as well as cleaning up some
complex code.

Ksmserver calls into kwin, rather than having kwin watch ksmserver state
to allow us make sure it's race free.

Reviewers: #kwin, zzag

Reviewed By: #kwin, zzag

Subscribers: romangg, zzag, kwin

Tags: #kwin

Differential Revision: https://phabr...
parent cf279180
......@@ -549,6 +549,7 @@ qt5_add_dbus_adaptor(kwin_KDEINIT_SRCS org.kde.kwin.ColorCorrect.xml colorcorrec
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)
......
......@@ -558,7 +558,7 @@ bool Workspace::allowClientActivation(const KWin::AbstractClient *c, xcb_timesta
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::allowClientActivation(const KWin::AbstractClient *c, xcb_timesta
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();
......
......@@ -121,7 +121,7 @@ void Activities::toggleClientOnActivity(X11Client *c, const QString &activity, b
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::start(const QString &id)
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 @@ bool Activities::stop(const QString &id)
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;
......
......@@ -138,6 +138,16 @@ enum class SwipeDirection {
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()
{
......
......@@ -480,9 +480,6 @@ KWIN_EXPORT int kdemain(int argc, char * argv[])
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();
}
......
......@@ -432,7 +432,8 @@ bool X11Client::manage(xcb_window_t w, bool isMapped)
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 @@ bool X11Client::manage(xcb_window_t w, bool isMapped)
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<X11Client *>(c)) {
......
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.KWin.Session">
<method name="setState">
<!-- Sets state of the session should be one of:
0 = normal
1 = saving
2= quitting
-->
<arg name="state" type="u" direction="in" />
</method>
</interface>
</node>
......@@ -29,14 +29,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "workspace.h"
#include "x11client.h"
#include <QDebug>
#include <QFile>
#include <QSocketNotifier>
#include <QSessionManager>
#include <QDBusConnection>
#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 @@ static NET::WindowType txtToWindowType(const char* txt)
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 @@ void Workspace::saveState(QSessionManager &sm)
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 @@ void Workspace::saveState(QSessionManager &sm)
}
}
// 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 @@ SessionInfo* Workspace::takeSessionInfo(X11Client *c)
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
......
......@@ -28,16 +28,31 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <netwm_def.h>
#include <QRect>
#include <X11/SM/SMlib.h>
#include <fixx11h.h>
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 @@ enum SMSavePhase {
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
......@@ -118,7 +118,6 @@ Workspace::Workspace(const QString &sessionKey)
, 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 @@ Workspace::Workspace(const QString &sessionKey)
, 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<void> reparseConfigFuture = QtConcurrent::run(options, &Options::reparseConfiguration);
......@@ -156,7 +156,6 @@ Workspace::Workspace(const QString &sessionKey)
if (!sessionKey.isEmpty())
loadSessionInfo(sessionKey);
connect(qApp, &QGuiApplication::commitDataRequest, this, &Workspace::commitData);
connect(qApp, &QGuiApplication::saveStateRequest, this, &Workspace::saveState);
RuleBook::create(this)->load();
......
......@@ -244,6 +244,8 @@ public:
void stackScreenEdgesUnderOverrideRedirect();
SessionManager *sessionManager() const;
public:
QPoint cascadeOffset(const AbstractClient *c) const;
......@@ -339,11 +341,8 @@ public:
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 @@ private Q_SLOTS:
// session management
void saveState(QSessionManager &sm);
void commitData(QSessionManager &sm);
Q_SIGNALS:
/**
......@@ -616,7 +614,7 @@ private:
bool was_user_interaction;
QScopedPointer<X11EventFilter> m_wasUserInteractionFilter;
bool session_saving;
int session_active_client;
int session_desktop;
......@@ -666,6 +664,7 @@ private:
QList<X11EventFilter *> m_genericEventFilters;
QScopedPointer<X11EventFilter> m_movingClientFilter;
SessionManager *m_sessionManager;
private:
friend bool performTransiencyCheck();
friend Workspace *workspace();
......@@ -742,14 +741,9 @@ inline bool Workspace::wasUserInteraction() const
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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment