Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qxcbsessionmanager.cpp
Go to the documentation of this file.
1// Copyright (C) 2013 Teo Mrnjavac <teo@kde.org>
2// Copyright (C) 2016 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4// Qt-Security score:significant reason:default
5
7
8#ifndef QT_NO_SESSIONMANAGER
9
10#include <QtCore/qvarlengtharray.h>
11#include <qpa/qwindowsysteminterface.h>
12
13#include <qguiapplication.h>
14#include <qdatetime.h>
15#include <qfileinfo.h>
16#include <qplatformdefs.h>
17#include <qsocketnotifier.h>
18#include <X11/SM/SMlib.h>
19#include <errno.h> // ERANGE
20
21#include <cerrno> // ERANGE
22
23using namespace Qt::StringLiterals;
24
25namespace {
26
27class QSmSocketReceiver : public QObject
28{
29 Q_OBJECT
30public:
31 QSmSocketReceiver(int socket)
32 {
33 QSocketNotifier* sn = new QSocketNotifier(socket, QSocketNotifier::Read, this);
34 connect(sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(socketActivated()));
35 }
36
37public Q_SLOTS:
38 void socketActivated();
39};
40
41
42SmcConn smcConnection = nullptr;
43bool sm_interactionActive;
44bool sm_smActive;
45int sm_interactStyle;
46int sm_saveType;
47bool sm_cancel;
48bool sm_waitingForInteraction;
49bool sm_isshutdown;
50bool sm_phase2;
51bool sm_in_phase2;
52bool qt_sm_blockUserInput = false;
53
54QSmSocketReceiver* sm_receiver = nullptr;
55
56void resetSmState();
57void sm_setProperty(const char *name, const char *type,
58 int num_vals, SmPropValue *vals);
59void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData,
60 int saveType, Bool shutdown , int interactStyle, Bool fast);
61void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData) ;
62void sm_dieCallback(SmcConn smcConn, SmPointer clientData) ;
63void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData);
64void sm_saveCompleteCallback(SmcConn smcConn, SmPointer clientData);
65void sm_interactCallback(SmcConn smcConn, SmPointer clientData);
66void sm_performSaveYourself(QXcbSessionManager*);
67
68void resetSmState()
69{
70 sm_waitingForInteraction = false;
71 sm_interactionActive = false;
72 sm_interactStyle = SmInteractStyleNone;
73 sm_smActive = false;
74 qt_sm_blockUserInput = false;
75 sm_isshutdown = false;
76 sm_phase2 = false;
77 sm_in_phase2 = false;
78}
79
80
81// theoretically it's possible to set several properties at once. For
82// simplicity, however, we do just one property at a time
83void sm_setProperty(const char *name, const char *type,
84 int num_vals, SmPropValue *vals)
85{
86 if (num_vals) {
87 SmProp prop;
88 prop.name = const_cast<char*>(name);
89 prop.type = const_cast<char*>(type);
90 prop.num_vals = num_vals;
91 prop.vals = vals;
92
93 SmProp* props[1];
94 props[0] = &prop;
95 SmcSetProperties(smcConnection, 1, props);
96 } else {
97 char* names[1];
98 names[0] = const_cast<char*>(name);
99 SmcDeleteProperties(smcConnection, 1, names);
100 }
101}
102
103void sm_setProperty(const QString &name, const QString &value)
104{
105 QByteArray v = value.toUtf8();
106 SmPropValue prop;
107 prop.length = v.size();
108 prop.value = (SmPointer) const_cast<char *>(v.constData());
109 sm_setProperty(name.toLatin1().data(), SmARRAY8, 1, &prop);
110}
111
112void sm_setProperty(const QString &name, const QStringList &value)
113{
114 SmPropValue *prop = new SmPropValue[value.size()];
115 int count = 0;
116 QList<QByteArray> vl;
117 vl.reserve(value.size());
118 for (QStringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
119 prop[count].length = (*it).size();
120 vl.append((*it).toUtf8());
121 prop[count].value = (char*)vl.constLast().data();
122 ++count;
123 }
124 sm_setProperty(name.toLatin1().data(), SmLISTofARRAY8, count, prop);
125 delete [] prop;
126}
127
128
129// workaround for broken libsm, see below
130struct QT_smcConn {
131 unsigned int save_yourself_in_progress : 1;
132 unsigned int shutdown_in_progress : 1;
133};
134
135void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData,
136 int saveType, Bool shutdown , int interactStyle, Bool /*fast*/)
137{
138 if (smcConn != smcConnection)
139 return;
140 sm_cancel = false;
141 sm_smActive = true;
142 sm_isshutdown = shutdown;
143 sm_saveType = saveType;
144 sm_interactStyle = interactStyle;
145
146 // ugly workaround for broken libSM. libSM should do that _before_
147 // actually invoking the callback in sm_process.c
148 ((QT_smcConn*)smcConn)->save_yourself_in_progress = true;
149 if (sm_isshutdown)
150 ((QT_smcConn*)smcConn)->shutdown_in_progress = true;
151
152 sm_performSaveYourself((QXcbSessionManager*) clientData);
153 if (!sm_isshutdown) // we cannot expect a confirmation message in that case
154 resetSmState();
155}
156
157void sm_performSaveYourself(QXcbSessionManager *sm)
158{
159 if (sm_isshutdown)
160 qt_sm_blockUserInput = true;
161
162 // generate a new session key
163 timeval tv;
164 gettimeofday(&tv, nullptr);
165 sm->setSessionKey(QString::number(qulonglong(tv.tv_sec)) +
166 u'_' +
167 QString::number(qulonglong(tv.tv_usec)));
168
169 QStringList arguments = QCoreApplication::arguments();
170 QString argument0 = arguments.isEmpty() ? QCoreApplication::applicationFilePath()
171 : arguments.at(0);
172
173 // tell the session manager about our program in best POSIX style
174 sm_setProperty(QString::fromLatin1(SmProgram), argument0);
175 // tell the session manager about our user as well.
176 struct passwd *entryPtr = nullptr;
177#if defined(_POSIX_THREAD_SAFE_FUNCTIONS) && (_POSIX_THREAD_SAFE_FUNCTIONS - 0 > 0)
178 QVarLengthArray<char, 1024> buf(qMax<long>(sysconf(_SC_GETPW_R_SIZE_MAX), 1024L));
179 struct passwd entry;
180 while (getpwuid_r(geteuid(), &entry, buf.data(), buf.size(), &entryPtr) == ERANGE) {
181 if (buf.size() >= 32768) {
182 // too big already, fail
183 static char badusername[] = "";
184 entryPtr = &entry;
185 entry.pw_name = badusername;
186 break;
187 }
188
189 // retry with a bigger buffer
190 buf.resize(buf.size() * 2);
191 }
192#else
193 entryPtr = getpwuid(geteuid());
194#endif
195 if (entryPtr)
196 sm_setProperty(QString::fromLatin1(SmUserID), QString::fromLocal8Bit(entryPtr->pw_name));
197
198 // generate a restart and discard command that makes sense
199 QStringList restart;
200 restart << argument0 << "-session"_L1 << sm->sessionId() + u'_' + sm->sessionKey();
201
202 QFileInfo fi(QCoreApplication::applicationFilePath());
203 if (qAppName().compare(fi.fileName(), Qt::CaseInsensitive) != 0)
204 restart << "-name"_L1 << qAppName();
205 sm->setRestartCommand(restart);
206 QStringList discard;
207 sm->setDiscardCommand(discard);
208
209 switch (sm_saveType) {
210 case SmSaveBoth:
211 sm->appCommitData();
212 if (sm_isshutdown && sm_cancel)
213 break; // we cancelled the shutdown, no need to save state
214 Q_FALLTHROUGH();
215 case SmSaveLocal:
216 sm->appSaveState();
217 break;
218 case SmSaveGlobal:
219 sm->appCommitData();
220 break;
221 default:
222 break;
223 }
224
225 if (sm_phase2 && !sm_in_phase2) {
226 SmcRequestSaveYourselfPhase2(smcConnection, sm_saveYourselfPhase2Callback, (SmPointer*) sm);
227 qt_sm_blockUserInput = false;
228 } else {
229 // close eventual interaction monitors and cancel the
230 // shutdown, if required. Note that we can only cancel when
231 // performing a shutdown, it does not work for checkpoints
232 if (sm_interactionActive) {
233 SmcInteractDone(smcConnection, sm_isshutdown && sm_cancel);
234 sm_interactionActive = false;
235 } else if (sm_cancel && sm_isshutdown) {
237 SmcInteractDone(smcConnection, True);
238 sm_interactionActive = false;
239 }
240 }
241
242 // set restart and discard command in session manager
243 sm_setProperty(QString::fromLatin1(SmRestartCommand), sm->restartCommand());
244 sm_setProperty(QString::fromLatin1(SmDiscardCommand), sm->discardCommand());
245
246 // set the restart hint
247 SmPropValue prop;
248 prop.length = sizeof(int);
249 int value = sm->restartHint();
250 prop.value = (SmPointer) &value;
251 sm_setProperty(SmRestartStyleHint, SmCARD8, 1, &prop);
252
253 // we are done
254 SmcSaveYourselfDone(smcConnection, !sm_cancel);
255 }
256}
257
258void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */)
259{
260 if (smcConn != smcConnection)
261 return;
262 resetSmState();
263 QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
264}
265
266void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData)
267{
268 if (smcConn != smcConnection)
269 return;
270 if (sm_waitingForInteraction)
272 resetSmState();
273}
274
275void sm_saveCompleteCallback(SmcConn smcConn, SmPointer /*clientData */)
276{
277 if (smcConn != smcConnection)
278 return;
279 resetSmState();
280}
281
282void sm_interactCallback(SmcConn smcConn, SmPointer clientData)
283{
284 if (smcConn != smcConnection)
285 return;
286 if (sm_waitingForInteraction)
288}
289
290void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData)
291{
292 if (smcConn != smcConnection)
293 return;
294 sm_in_phase2 = true;
295 sm_performSaveYourself((QXcbSessionManager *) clientData);
296}
297
298void QSmSocketReceiver::socketActivated()
299{
300 IceProcessMessages(SmcGetIceConnection(smcConnection), nullptr, nullptr);
301}
302
303} // namespace
304
305// QXcbSessionManager starts here
306
307QXcbSessionManager::QXcbSessionManager(const QString &id, const QString &key)
309 , m_eventLoop(nullptr)
310{
311 resetSmState();
312 char cerror[256];
313 char* myId = nullptr;
314 QByteArray b_id = id.toLatin1();
315 char* prevId = b_id.data();
316
317 SmcCallbacks cb;
318 cb.save_yourself.callback = sm_saveYourselfCallback;
319 cb.save_yourself.client_data = (SmPointer) this;
320 cb.die.callback = sm_dieCallback;
321 cb.die.client_data = (SmPointer) this;
322 cb.save_complete.callback = sm_saveCompleteCallback;
323 cb.save_complete.client_data = (SmPointer) this;
324 cb.shutdown_cancelled.callback = sm_shutdownCancelledCallback;
325 cb.shutdown_cancelled.client_data = (SmPointer) this;
326
327 // avoid showing a warning message below
328 if (!qEnvironmentVariableIsSet("SESSION_MANAGER"))
329 return;
330
331 smcConnection = SmcOpenConnection(nullptr, nullptr, 1, 0,
332 SmcSaveYourselfProcMask |
333 SmcDieProcMask |
334 SmcSaveCompleteProcMask |
335 SmcShutdownCancelledProcMask,
336 &cb,
337 prevId,
338 &myId,
339 256, cerror);
340
341 setSessionId(QString::fromLatin1(myId));
342 ::free(myId); // it was allocated by C
343
344 QString error = QString::fromLocal8Bit(cerror);
345 if (!smcConnection)
346 qWarning("Qt: Session management error: %s", qPrintable(error));
347 else
348 sm_receiver = new QSmSocketReceiver(IceConnectionNumber(SmcGetIceConnection(smcConnection)));
349}
350
352{
353 if (smcConnection)
354 SmcCloseConnection(smcConnection, 0, nullptr);
355 smcConnection = nullptr;
356 delete sm_receiver;
357}
358
359
361{
362 return (void*) smcConnection;
363}
364
366{
367 if (sm_interactionActive)
368 return true;
369
370 if (sm_waitingForInteraction)
371 return false;
372
373 if (sm_interactStyle == SmInteractStyleAny) {
374 sm_waitingForInteraction = SmcInteractRequest(smcConnection,
375 SmDialogNormal,
376 sm_interactCallback,
377 (SmPointer*) this);
378 }
379 if (sm_waitingForInteraction) {
380 QEventLoop eventLoop;
381 m_eventLoop = &eventLoop;
382 eventLoop.exec();
383 m_eventLoop = nullptr;
384
385 sm_waitingForInteraction = false;
386 if (sm_smActive) { // not cancelled
387 sm_interactionActive = true;
388 qt_sm_blockUserInput = false;
389 return true;
390 }
391 }
392 return false;
393}
394
396{
397 if (sm_interactionActive)
398 return true;
399
400 if (sm_waitingForInteraction)
401 return false;
402
403 if (sm_interactStyle == SmInteractStyleAny || sm_interactStyle == SmInteractStyleErrors) {
404 sm_waitingForInteraction = SmcInteractRequest(smcConnection,
405 SmDialogError,
406 sm_interactCallback,
407 (SmPointer*) this);
408 }
409 if (sm_waitingForInteraction) {
410 QEventLoop eventLoop;
411 m_eventLoop = &eventLoop;
412 eventLoop.exec();
413 m_eventLoop = nullptr;
414
415 sm_waitingForInteraction = false;
416 if (sm_smActive) { // not cancelled
417 sm_interactionActive = true;
418 qt_sm_blockUserInput = false;
419 return true;
420 }
421 }
422 return false;
423}
424
426{
427 if (sm_interactionActive) {
428 SmcInteractDone(smcConnection, False);
429 sm_interactionActive = false;
430 if (sm_smActive && sm_isshutdown)
431 qt_sm_blockUserInput = true;
432 }
433}
434
436{
437 sm_cancel = true;
438}
439
440void QXcbSessionManager::setManagerProperty(const QString &name, const QString &value)
441{
442 sm_setProperty(name, value);
443}
444
445void QXcbSessionManager::setManagerProperty(const QString &name, const QStringList &value)
446{
447 sm_setProperty(name, value);
448}
449
451{
452 return sm_in_phase2;
453}
454
456{
457 sm_phase2 = true;
458}
459
461{
462 m_eventLoop->exit();
463}
464
465#include "qxcbsessionmanager.moc"
466
467#endif
QXcbSessionManager(const QString &id, const QString &key)
void requestPhase2() override
bool isPhase2() const override
void setManagerProperty(const QString &name, const QString &value) override
bool allowsInteraction() override
bool allowsErrorInteraction() override