QortalOS Brooklyn for Raspberry Pi 4
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

580 lines
18 KiB

/*
ksmserver - the KDE session management server
SPDX-FileCopyrightText: 2000 Matthias Ettrich <[email protected]>
SPDX-FileContributor: Oswald Buddenhagen <[email protected]>
some code taken from the dcopserver (part of the KDE libraries), which is
SPDX-FileCopyrightText: 1999 Matthias Ettrich <[email protected]>
SPDX-FileCopyrightText: 1999 Preston Brown <[email protected]>
SPDX-License-Identifier: MIT
*/
#include <config-ksmserver.h>
#include <config-unix.h> // HAVE_LIMITS_H
#include <config-workspace.h>
#include <ksmserver_debug.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <QApplication>
#include <QDesktopWidget>
#include <QFile>
#include <QFutureWatcher>
#include <QTimer>
#include <QtConcurrentRun>
#include "client.h"
#include "global.h"
#include "server.h"
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KNotification>
#include <KSharedConfig>
#include <KUserTimestamp>
#include <kdisplaymanager.h>
#include "kwinsession_interface.h"
#include "logoutprompt_interface.h"
#include "shutdown_interface.h"
enum KWinSessionState {
Normal = 0,
Saving = 1,
Quitting = 2,
};
void KSMServer::logout(int confirm, int sdtype, int sdmode)
{
// KDE5: remove me
if (sdtype == KWorkSpace::ShutdownTypeLogout)
sdtype = KWorkSpace::ShutdownTypeNone;
shutdown((KWorkSpace::ShutdownConfirm)confirm, (KWorkSpace::ShutdownType)sdtype, (KWorkSpace::ShutdownMode)sdmode);
}
bool KSMServer::closeSession()
{
qCDebug(KSMSERVER) << "Close session called. Current state is:" << state;
Q_ASSERT(calledFromDBus());
setDelayedReply(true);
const QDBusMessage callerContext = message();
auto conn = QSharedPointer<QMetaObject::Connection>::create(QMetaObject::Connection());
*conn = connect(this, &KSMServer::logoutFinished, this, [callerContext, conn](bool sessionClosed) {
auto reply = callerContext.createReply(sessionClosed);
QDBusConnection::sessionBus().send(reply);
QObject::disconnect(*conn);
});
performLogout();
return false;
}
bool KSMServer::canShutdown()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
config->reparseConfiguration(); // config may have changed in the KControl module
KConfigGroup cg(config, "General");
return cg.readEntry("offerShutdown", true) && KDisplayManager().canShutdown();
}
bool KSMServer::isShuttingDown() const
{
return state >= Shutdown;
}
// this method exists purely for compatibility
void KSMServer::shutdown(KWorkSpace::ShutdownConfirm confirm, KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode)
{
qCDebug(KSMSERVER) << "Shutdown called with confirm " << confirm << " type " << sdtype << " and mode " << sdmode;
if (state >= Shutdown) // already performing shutdown
return;
if (state != Idle) // performing startup
{
return;
}
KSharedConfig::Ptr config = KSharedConfig::openConfig();
config->reparseConfiguration(); // config may have changed in the KControl module
KConfigGroup cg(config, "General");
bool logoutConfirmed = (confirm == KWorkSpace::ShutdownConfirmYes ? false
: confirm == KWorkSpace::ShutdownConfirmNo ? true
: !cg.readEntry("confirmLogout", true));
int shutdownType = (sdtype != KWorkSpace::ShutdownTypeDefault ? sdtype : cg.readEntry("shutdownType", (int)KWorkSpace::ShutdownType::ShutdownTypeLogout));
if (!logoutConfirmed) {
OrgKdeLogoutPromptInterface logoutPrompt(QStringLiteral("org.kde.LogoutPrompt"), QStringLiteral("/LogoutPrompt"), QDBusConnection::sessionBus());
switch (shutdownType) {
case KWorkSpace::ShutdownTypeHalt:
logoutPrompt.promptShutDown();
break;
case KWorkSpace::ShutdownTypeReboot:
logoutPrompt.promptReboot();
break;
case KWorkSpace::ShutdownTypeNone:
Q_FALLTHROUGH();
default:
logoutPrompt.promptLogout();
break;
}
} else {
OrgKdeShutdownInterface shutdownIface(QStringLiteral("org.kde.Shutdown"), QStringLiteral("/Shutdown"), QDBusConnection::sessionBus());
switch (shutdownType) {
case KWorkSpace::ShutdownTypeHalt:
shutdownIface.logoutAndShutdown();
break;
case KWorkSpace::ShutdownTypeReboot:
shutdownIface.logoutAndReboot();
break;
case KWorkSpace::ShutdownTypeNone:
Q_FALLTHROUGH();
default:
shutdownIface.logout();
break;
}
}
}
void KSMServer::performLogout()
{
if (state >= Shutdown) { // already performing shutdown
return;
}
if (state != Idle) {
QTimer::singleShot(1000, this, &KSMServer::performLogout);
}
auto setStateReply = m_kwinInterface->setState(KWinSessionState::Saving);
state = Shutdown;
// shall we save the session on logout?
KConfigGroup cg(KSharedConfig::openConfig(), "General");
saveSession = (cg.readEntry("loginMode", QStringLiteral("restorePreviousLogout")) == QLatin1String("restorePreviousLogout"));
qCDebug(KSMSERVER) << "saveSession is " << saveSession;
if (saveSession)
sessionGroup = QStringLiteral("Session: ") + QString::fromLocal8Bit(SESSION_PREVIOUS_LOGOUT);
// Set the real desktop background to black so that exit looks
// clean regardless of what was on "our" desktop.
QPalette palette;
palette.setColor(QApplication::desktop()->backgroundRole(), Qt::black);
QApplication::setPalette(palette);
saveType = saveSession ? SmSaveBoth : SmSaveGlobal;
#ifndef NO_LEGACY_SESSION_MANAGEMENT
performLegacySessionSave();
#endif
startProtection();
// Tell KWin to start saving before we start tearing down clients
// as any "Save changes?" prompt might meddle with the state
if (saveSession) {
setStateReply.waitForFinished(); // do we have to wait for this to finish?
qCDebug(KSMSERVER) << "Telling KWin we're about to save session" << currentSession();
auto saveSessionCall = m_kwinInterface->aboutToSaveSession(currentSession());
// We need to wait for KWin to save the initial state, e.g. active client and
// current desktop before we signal any clients to quit. They might bring up
// "Save changes?" prompts altering the state.
// KWin doesn't talk to KSMServer directly anymore, so this won't deadlock.
saveSessionCall.waitForFinished();
}
const auto pendingClients = clients;
for (KSMClient *c : pendingClients) {
c->resetState();
SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
}
qCDebug(KSMSERVER) << "clients should be empty, " << clients.count();
if (clients.isEmpty())
completeShutdownOrCheckpoint();
}
void KSMServer::saveCurrentSession()
{
if (state != Idle)
return;
if (currentSession().isEmpty() || currentSession() == QString::fromLocal8Bit(SESSION_PREVIOUS_LOGOUT))
sessionGroup = QLatin1String("Session: ") + QString::fromLocal8Bit(SESSION_BY_USER);
state = Checkpoint;
saveType = SmSaveLocal;
saveSession = true;
#ifndef NO_LEGACY_SESSION_MANAGEMENT
performLegacySessionSave();
#endif
auto aboutToSaveCall = m_kwinInterface->aboutToSaveSession(currentSession());
aboutToSaveCall.waitForFinished();
const auto pendingClients = clients;
for (KSMClient *c : pendingClients) {
SmsSaveYourself(c->connection(), saveType, false, SmInteractStyleNone, false);
}
if (clients.isEmpty())
completeShutdownOrCheckpoint();
}
void KSMServer::saveCurrentSessionAs(const QString &session)
{
if (state != Idle)
return;
sessionGroup = QStringLiteral("Session: ") + session;
saveCurrentSession();
}
// callbacks
void KSMServer::saveYourselfDone(KSMClient *client, bool success)
{
if (state == Idle) {
// State saving when it's not shutdown or checkpoint. Probably
// a shutdown was canceled and the client is finished saving
// only now. Discard the saved state in order to avoid
// the saved data building up.
QStringList discard = client->discardCommand();
if (!discard.isEmpty())
executeCommand(discard);
return;
}
if (success) {
client->saveYourselfDone = true;
completeShutdownOrCheckpoint();
} else {
// fake success to make KDE's logout not block with broken
// apps. A perfect ksmserver would display a warning box at
// the very end.
client->saveYourselfDone = true;
completeShutdownOrCheckpoint();
}
startProtection();
}
void KSMServer::interactRequest(KSMClient *client, int /*dialogType*/)
{
if (state == Shutdown || state == ClosingSubSession)
client->pendingInteraction = true;
else
SmsInteract(client->connection());
handlePendingInteractions();
}
void KSMServer::interactDone(KSMClient *client, bool cancelShutdown_)
{
if (client != clientInteracting)
return; // should not happen
clientInteracting = nullptr;
if (cancelShutdown_)
cancelShutdown(client);
else
handlePendingInteractions();
}
void KSMServer::phase2Request(KSMClient *client)
{
client->waitForPhase2 = true;
client->wasPhase2 = true;
completeShutdownOrCheckpoint();
}
void KSMServer::handlePendingInteractions()
{
if (clientInteracting)
return;
foreach (KSMClient *c, clients) {
if (c->pendingInteraction) {
clientInteracting = c;
c->pendingInteraction = false;
break;
}
}
if (clientInteracting) {
endProtection();
SmsInteract(clientInteracting->connection());
} else {
startProtection();
}
}
void KSMServer::cancelShutdown(KSMClient *c)
{
clientInteracting = nullptr;
qCDebug(KSMSERVER) << state;
if (state == ClosingSubSession) {
clientsToKill.clear();
clientsToSave.clear();
Q_EMIT subSessionCloseCanceled();
} else {
qCDebug(KSMSERVER) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown.";
KNotification::event(QStringLiteral("cancellogout"), i18n("Logout canceled by '%1'", c->program()), QPixmap(), nullptr, KNotification::DefaultEvent);
foreach (KSMClient *c, clients) {
SmsShutdownCancelled(c->connection());
if (c->saveYourselfDone) {
// Discard also saved state.
QStringList discard = c->discardCommand();
if (!discard.isEmpty())
executeCommand(discard);
}
}
}
state = Idle;
m_kwinInterface->setState(KWinSessionState::Normal);
Q_EMIT logoutFinished(false);
}
void KSMServer::startProtection()
{
KSharedConfig::Ptr config = KSharedConfig::openConfig();
config->reparseConfiguration(); // config may have changed in the KControl module
KConfigGroup cg(config, "General");
int timeout = cg.readEntry("clientShutdownTimeoutSecs", 15) * 1000;
protectionTimer.setSingleShot(true);
protectionTimer.start(timeout);
}
void KSMServer::endProtection()
{
protectionTimer.stop();
}
/*
Internal protection slot, invoked when clients do not react during
shutdown.
*/
void KSMServer::protectionTimeout()
{
if ((state != Shutdown && state != Checkpoint && state != ClosingSubSession) || clientInteracting)
return;
foreach (KSMClient *c, clients) {
if (!c->saveYourselfDone && !c->waitForPhase2) {
qCDebug(KSMSERVER) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")";
c->saveYourselfDone = true;
}
}
completeShutdownOrCheckpoint();
startProtection();
}
void KSMServer::completeShutdownOrCheckpoint()
{
qCDebug(KSMSERVER) << "completeShutdownOrCheckpoint called";
if (state != Shutdown && state != Checkpoint && state != ClosingSubSession)
return;
QList<KSMClient *> pendingClients;
if (state == ClosingSubSession)
pendingClients = clientsToSave;
else
pendingClients = clients;
foreach (KSMClient *c, pendingClients) {
if (!c->saveYourselfDone && !c->waitForPhase2)
return; // not done yet
}
// do phase 2
bool waitForPhase2 = false;
foreach (KSMClient *c, pendingClients) {
if (!c->saveYourselfDone && c->waitForPhase2) {
c->waitForPhase2 = false;
SmsSaveYourselfPhase2(c->connection());
waitForPhase2 = true;
}
}
if (waitForPhase2)
return;
if (saveSession)
storeSession();
else
discardSession();
qCDebug(KSMSERVER) << "state is " << state;
if (state == Shutdown) {
KNotification *n = KNotification::event(QStringLiteral("exitkde"), QString(), QPixmap(), nullptr, KNotification::DefaultEvent); // Plasma says good bye
connect(n, &KNotification::closed, this, &KSMServer::startKilling);
state = WaitingForKNotify;
// https://bugs.kde.org/show_bug.cgi?id=228005
// if sound is not working for some reason (e.g. no phonon
// backends are installed) the closed() signal never happens
// and logoutSoundFinished() never gets called. Add this timer to make
// sure the shutdown procedure continues even if sound system is broken.
QTimer::singleShot(5000, this, [=] {
if (state == WaitingForKNotify) {
n->deleteLater();
startKilling();
}
});
} else if (state == Checkpoint) {
foreach (KSMClient *c, clients) {
SmsSaveComplete(c->connection());
}
state = Idle;
} else { // ClosingSubSession
startKillingSubSession();
}
}
void KSMServer::startKilling()
{
qCDebug(KSMSERVER) << "Starting killing clients";
if (state == Killing) {
// we are already killing
return;
}
// kill all clients
state = Killing;
m_kwinInterface->setState(KWinSessionState::Quitting);
foreach (KSMClient *c, clients) {
qCDebug(KSMSERVER) << "startKilling: client " << c->program() << "(" << c->clientId() << ")";
SmsDie(c->connection());
}
qCDebug(KSMSERVER) << " We killed all clients. We have now clients.count()=" << clients.count() << Qt::endl;
completeKilling();
QTimer::singleShot(10000, this, &KSMServer::timeoutQuit);
}
void KSMServer::completeKilling()
{
qCDebug(KSMSERVER) << "KSMServer::completeKilling clients.count()=" << clients.count() << Qt::endl;
if (state == Killing) {
if (!clients.isEmpty()) // still waiting for clients to go away
return;
killingCompleted();
}
}
// shutdown is fully complete
void KSMServer::killingCompleted()
{
Q_EMIT logoutFinished(true);
qApp->quit();
}
void KSMServer::timeoutQuit()
{
foreach (KSMClient *c, clients) {
qCWarning(KSMSERVER) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")";
}
killingCompleted();
}
void KSMServer::saveSubSession(const QString &name, QStringList saveAndClose, QStringList saveOnly)
{
if (state != Idle) { // performing startup
qCDebug(KSMSERVER) << "not idle!" << state;
return;
}
qCDebug(KSMSERVER) << name << saveAndClose << saveOnly;
state = ClosingSubSession;
saveType = SmSaveBoth; // both or local? what oes it mean?
saveSession = true;
sessionGroup = QStringLiteral("SubSession: ") + name;
#ifndef NO_LEGACY_SESSION_MANAGEMENT
// performLegacySessionSave(); FIXME
#endif
startProtection();
foreach (KSMClient *c, clients) {
if (saveAndClose.contains(QString::fromLocal8Bit(c->clientId()))) {
c->resetState();
SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
clientsToSave << c;
clientsToKill << c;
} else if (saveOnly.contains(QString::fromLocal8Bit(c->clientId()))) {
c->resetState();
SmsSaveYourself(c->connection(), saveType, true, SmInteractStyleAny, false);
clientsToSave << c;
}
}
completeShutdownOrCheckpoint();
}
void KSMServer::startKillingSubSession()
{
qCDebug(KSMSERVER) << "Starting killing clients";
// kill all clients
state = KillingSubSession;
foreach (KSMClient *c, clientsToKill) {
qCDebug(KSMSERVER) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")";
SmsDie(c->connection());
}
qCDebug(KSMSERVER) << " We killed some clients. We have now clients.count()=" << clients.count() << Qt::endl;
completeKillingSubSession();
QTimer::singleShot(10000, this, &KSMServer::signalSubSessionClosed);
}
void KSMServer::completeKillingSubSession()
{
qCDebug(KSMSERVER) << "KSMServer::completeKillingSubSession clients.count()=" << clients.count() << Qt::endl;
if (state == KillingSubSession) {
if (!clientsToKill.isEmpty()) {
return; // still waiting for clients to go away
}
signalSubSessionClosed();
}
}
void KSMServer::signalSubSessionClosed()
{
if (state != KillingSubSession)
return;
clientsToKill.clear();
clientsToSave.clear();
// TODO tell the subSession manager the close request was carried out
// so that plasma can close its stuff
state = Idle;
qCDebug(KSMSERVER) << state;
Q_EMIT subSessionClosed();
}