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