blob: e9697f505be2083c3c08e098570a67e11845f295 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2013 Teo Mrnjavac <teo@kde.org>
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the plugins of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qxcbsessionmanager.h"
#ifndef QT_NO_SESSIONMANAGER
#include <qpa/qwindowsysteminterface.h>
#include <qguiapplication.h>
#include <qdatetime.h>
#include <qfileinfo.h>
#include <qplatformdefs.h>
#include <qsocketnotifier.h>
#include <X11/SM/SMlib.h>
#include <errno.h> // ERANGE
#include <cerrno> // ERANGE
class QSmSocketReceiver : public QObject
{
Q_OBJECT
public:
QSmSocketReceiver(int socket)
{
QSocketNotifier* sn = new QSocketNotifier(socket, QSocketNotifier::Read, this);
connect(sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(socketActivated()));
}
public Q_SLOTS:
void socketActivated();
};
static SmcConn smcConnection = nullptr;
static bool sm_interactionActive;
static bool sm_smActive;
static int sm_interactStyle;
static int sm_saveType;
static bool sm_cancel;
static bool sm_waitingForInteraction;
static bool sm_isshutdown;
static bool sm_phase2;
static bool sm_in_phase2;
bool qt_sm_blockUserInput = false;
static QSmSocketReceiver* sm_receiver = nullptr;
static void resetSmState();
static void sm_setProperty(const char *name, const char *type,
int num_vals, SmPropValue *vals);
static void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData,
int saveType, Bool shutdown , int interactStyle, Bool fast);
static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData) ;
static void sm_dieCallback(SmcConn smcConn, SmPointer clientData) ;
static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData);
static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer clientData);
static void sm_interactCallback(SmcConn smcConn, SmPointer clientData);
static void sm_performSaveYourself(QXcbSessionManager*);
static void resetSmState()
{
sm_waitingForInteraction = false;
sm_interactionActive = false;
sm_interactStyle = SmInteractStyleNone;
sm_smActive = false;
qt_sm_blockUserInput = false;
sm_isshutdown = false;
sm_phase2 = false;
sm_in_phase2 = false;
}
// theoretically it's possible to set several properties at once. For
// simplicity, however, we do just one property at a time
static void sm_setProperty(const char *name, const char *type,
int num_vals, SmPropValue *vals)
{
if (num_vals) {
SmProp prop;
prop.name = const_cast<char*>(name);
prop.type = const_cast<char*>(type);
prop.num_vals = num_vals;
prop.vals = vals;
SmProp* props[1];
props[0] = &prop;
SmcSetProperties(smcConnection, 1, props);
} else {
char* names[1];
names[0] = const_cast<char*>(name);
SmcDeleteProperties(smcConnection, 1, names);
}
}
static void sm_setProperty(const QString &name, const QString &value)
{
QByteArray v = value.toUtf8();
SmPropValue prop;
prop.length = v.length();
prop.value = (SmPointer) const_cast<char *>(v.constData());
sm_setProperty(name.toLatin1().data(), SmARRAY8, 1, &prop);
}
static void sm_setProperty(const QString &name, const QStringList &value)
{
SmPropValue *prop = new SmPropValue[value.count()];
int count = 0;
QList<QByteArray> vl;
vl.reserve(value.size());
for (QStringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
prop[count].length = (*it).length();
vl.append((*it).toUtf8());
prop[count].value = (char*)vl.constLast().data();
++count;
}
sm_setProperty(name.toLatin1().data(), SmLISTofARRAY8, count, prop);
delete [] prop;
}
// workaround for broken libsm, see below
struct QT_smcConn {
unsigned int save_yourself_in_progress : 1;
unsigned int shutdown_in_progress : 1;
};
static void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData,
int saveType, Bool shutdown , int interactStyle, Bool /*fast*/)
{
if (smcConn != smcConnection)
return;
sm_cancel = false;
sm_smActive = true;
sm_isshutdown = shutdown;
sm_saveType = saveType;
sm_interactStyle = interactStyle;
// ugly workaround for broken libSM. libSM should do that _before_
// actually invoking the callback in sm_process.c
((QT_smcConn*)smcConn)->save_yourself_in_progress = true;
if (sm_isshutdown)
((QT_smcConn*)smcConn)->shutdown_in_progress = true;
sm_performSaveYourself((QXcbSessionManager*) clientData);
if (!sm_isshutdown) // we cannot expect a confirmation message in that case
resetSmState();
}
static void sm_performSaveYourself(QXcbSessionManager *sm)
{
if (sm_isshutdown)
qt_sm_blockUserInput = true;
// generate a new session key
timeval tv;
gettimeofday(&tv, nullptr);
sm->setSessionKey(QString::number(qulonglong(tv.tv_sec)) +
QLatin1Char('_') +
QString::number(qulonglong(tv.tv_usec)));
QStringList arguments = QCoreApplication::arguments();
QString argument0 = arguments.isEmpty() ? QCoreApplication::applicationFilePath()
: arguments.at(0);
// tell the session manager about our program in best POSIX style
sm_setProperty(QString::fromLatin1(SmProgram), argument0);
// tell the session manager about our user as well.
struct passwd *entryPtr = nullptr;
#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0)
QVarLengthArray<char, 1024> buf(qMax<long>(sysconf(_SC_GETPW_R_SIZE_MAX), 1024L));
struct passwd entry;
while (getpwuid_r(geteuid(), &entry, buf.data(), buf.size(), &entryPtr) == ERANGE) {
if (buf.size() >= 32768) {
// too big already, fail
static char badusername[] = "";
entryPtr = &entry;
entry.pw_name = badusername;
break;
}
// retry with a bigger buffer
buf.resize(buf.size() * 2);
}
#else
entryPtr = getpwuid(geteuid());
#endif
if (entryPtr)
sm_setProperty(QString::fromLatin1(SmUserID), QString::fromLocal8Bit(entryPtr->pw_name));
// generate a restart and discard command that makes sense
QStringList restart;
restart << argument0 << QLatin1String("-session")
<< sm->sessionId() + QLatin1Char('_') + sm->sessionKey();
QFileInfo fi = QCoreApplication::applicationFilePath();
if (qAppName().compare(fi.fileName(), Qt::CaseInsensitive) != 0)
restart << QLatin1String("-name") << qAppName();
sm->setRestartCommand(restart);
QStringList discard;
sm->setDiscardCommand(discard);
switch (sm_saveType) {
case SmSaveBoth:
sm->appCommitData();
if (sm_isshutdown && sm_cancel)
break; // we cancelled the shutdown, no need to save state
// fall through
case SmSaveLocal:
sm->appSaveState();
break;
case SmSaveGlobal:
sm->appCommitData();
break;
default:
break;
}
if (sm_phase2 && !sm_in_phase2) {
SmcRequestSaveYourselfPhase2(smcConnection, sm_saveYourselfPhase2Callback, (SmPointer*) sm);
qt_sm_blockUserInput = false;
} else {
// close eventual interaction monitors and cancel the
// shutdown, if required. Note that we can only cancel when
// performing a shutdown, it does not work for checkpoints
if (sm_interactionActive) {
SmcInteractDone(smcConnection, sm_isshutdown && sm_cancel);
sm_interactionActive = false;
} else if (sm_cancel && sm_isshutdown) {
if (sm->allowsErrorInteraction()) {
SmcInteractDone(smcConnection, True);
sm_interactionActive = false;
}
}
// set restart and discard command in session manager
sm_setProperty(QString::fromLatin1(SmRestartCommand), sm->restartCommand());
sm_setProperty(QString::fromLatin1(SmDiscardCommand), sm->discardCommand());
// set the restart hint
SmPropValue prop;
prop.length = sizeof(int);
int value = sm->restartHint();
prop.value = (SmPointer) &value;
sm_setProperty(SmRestartStyleHint, SmCARD8, 1, &prop);
// we are done
SmcSaveYourselfDone(smcConnection, !sm_cancel);
}
}
static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */)
{
if (smcConn != smcConnection)
return;
resetSmState();
QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
}
static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData)
{
if (smcConn != smcConnection)
return;
if (sm_waitingForInteraction)
((QXcbSessionManager *) clientData)->exitEventLoop();
resetSmState();
}
static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer /*clientData */)
{
if (smcConn != smcConnection)
return;
resetSmState();
}
static void sm_interactCallback(SmcConn smcConn, SmPointer clientData)
{
if (smcConn != smcConnection)
return;
if (sm_waitingForInteraction)
((QXcbSessionManager *) clientData)->exitEventLoop();
}
static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData)
{
if (smcConn != smcConnection)
return;
sm_in_phase2 = true;
sm_performSaveYourself((QXcbSessionManager *) clientData);
}
void QSmSocketReceiver::socketActivated()
{
IceProcessMessages(SmcGetIceConnection(smcConnection), nullptr, nullptr);
}
// QXcbSessionManager starts here
QXcbSessionManager::QXcbSessionManager(const QString &id, const QString &key)
: QPlatformSessionManager(id, key)
, m_eventLoop(nullptr)
{
resetSmState();
char cerror[256];
char* myId = nullptr;
QByteArray b_id = id.toLatin1();
char* prevId = b_id.data();
SmcCallbacks cb;
cb.save_yourself.callback = sm_saveYourselfCallback;
cb.save_yourself.client_data = (SmPointer) this;
cb.die.callback = sm_dieCallback;
cb.die.client_data = (SmPointer) this;
cb.save_complete.callback = sm_saveCompleteCallback;
cb.save_complete.client_data = (SmPointer) this;
cb.shutdown_cancelled.callback = sm_shutdownCancelledCallback;
cb.shutdown_cancelled.client_data = (SmPointer) this;
// avoid showing a warning message below
if (!qEnvironmentVariableIsSet("SESSION_MANAGER"))
return;
smcConnection = SmcOpenConnection(nullptr, nullptr, 1, 0,
SmcSaveYourselfProcMask |
SmcDieProcMask |
SmcSaveCompleteProcMask |
SmcShutdownCancelledProcMask,
&cb,
prevId,
&myId,
256, cerror);
setSessionId(QString::fromLatin1(myId));
::free(myId); // it was allocated by C
QString error = QString::fromLocal8Bit(cerror);
if (!smcConnection)
qWarning("Qt: Session management error: %s", qPrintable(error));
else
sm_receiver = new QSmSocketReceiver(IceConnectionNumber(SmcGetIceConnection(smcConnection)));
}
QXcbSessionManager::~QXcbSessionManager()
{
if (smcConnection)
SmcCloseConnection(smcConnection, 0, nullptr);
smcConnection = nullptr;
delete sm_receiver;
}
void* QXcbSessionManager::handle() const
{
return (void*) smcConnection;
}
bool QXcbSessionManager::allowsInteraction()
{
if (sm_interactionActive)
return true;
if (sm_waitingForInteraction)
return false;
if (sm_interactStyle == SmInteractStyleAny) {
sm_waitingForInteraction = SmcInteractRequest(smcConnection,
SmDialogNormal,
sm_interactCallback,
(SmPointer*) this);
}
if (sm_waitingForInteraction) {
QEventLoop eventLoop;
m_eventLoop = &eventLoop;
eventLoop.exec();
m_eventLoop = nullptr;
sm_waitingForInteraction = false;
if (sm_smActive) { // not cancelled
sm_interactionActive = true;
qt_sm_blockUserInput = false;
return true;
}
}
return false;
}
bool QXcbSessionManager::allowsErrorInteraction()
{
if (sm_interactionActive)
return true;
if (sm_waitingForInteraction)
return false;
if (sm_interactStyle == SmInteractStyleAny || sm_interactStyle == SmInteractStyleErrors) {
sm_waitingForInteraction = SmcInteractRequest(smcConnection,
SmDialogError,
sm_interactCallback,
(SmPointer*) this);
}
if (sm_waitingForInteraction) {
QEventLoop eventLoop;
m_eventLoop = &eventLoop;
eventLoop.exec();
m_eventLoop = nullptr;
sm_waitingForInteraction = false;
if (sm_smActive) { // not cancelled
sm_interactionActive = true;
qt_sm_blockUserInput = false;
return true;
}
}
return false;
}
void QXcbSessionManager::release()
{
if (sm_interactionActive) {
SmcInteractDone(smcConnection, False);
sm_interactionActive = false;
if (sm_smActive && sm_isshutdown)
qt_sm_blockUserInput = true;
}
}
void QXcbSessionManager::cancel()
{
sm_cancel = true;
}
void QXcbSessionManager::setManagerProperty(const QString &name, const QString &value)
{
sm_setProperty(name, value);
}
void QXcbSessionManager::setManagerProperty(const QString &name, const QStringList &value)
{
sm_setProperty(name, value);
}
bool QXcbSessionManager::isPhase2() const
{
return sm_in_phase2;
}
void QXcbSessionManager::requestPhase2()
{
sm_phase2 = true;
}
void QXcbSessionManager::exitEventLoop()
{
m_eventLoop->exit();
}
#include "qxcbsessionmanager.moc"
#endif