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

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