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

source code of qtbase/src/plugins/platforms/xcb/qxcbsessionmanager.cpp