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
57 class QSmSocketReceiver : public QObject
58 {
59 Q_OBJECT
60 public:
QSmSocketReceiver(int socket)61 QSmSocketReceiver(int socket)
62 {
63 QSocketNotifier* sn = new QSocketNotifier(socket, QSocketNotifier::Read, this);
64 connect(sn, SIGNAL(activated(QSocketDescriptor)), this, SLOT(socketActivated()));
65 }
66
67 public Q_SLOTS:
68 void socketActivated();
69 };
70
71
72 static SmcConn smcConnection = nullptr;
73 static bool sm_interactionActive;
74 static bool sm_smActive;
75 static int sm_interactStyle;
76 static int sm_saveType;
77 static bool sm_cancel;
78 static bool sm_waitingForInteraction;
79 static bool sm_isshutdown;
80 static bool sm_phase2;
81 static bool sm_in_phase2;
82 bool qt_sm_blockUserInput = false;
83
84 static QSmSocketReceiver* sm_receiver = nullptr;
85
86 static void resetSmState();
87 static void sm_setProperty(const char *name, const char *type,
88 int num_vals, SmPropValue *vals);
89 static void sm_saveYourselfCallback(SmcConn smcConn, SmPointer clientData,
90 int saveType, Bool shutdown , int interactStyle, Bool fast);
91 static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData) ;
92 static void sm_dieCallback(SmcConn smcConn, SmPointer clientData) ;
93 static void sm_shutdownCancelledCallback(SmcConn smcConn, SmPointer clientData);
94 static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer clientData);
95 static void sm_interactCallback(SmcConn smcConn, SmPointer clientData);
96 static void sm_performSaveYourself(QXcbSessionManager*);
97
resetSmState()98 static 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
sm_setProperty(const char * name,const char * type,int num_vals,SmPropValue * vals)113 static 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] = ∝
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
sm_setProperty(const QString & name,const QString & value)133 static 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.toLatin1().data(), SmARRAY8, 1, &prop);
140 }
141
sm_setProperty(const QString & name,const QStringList & value)142 static 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(value.size());
148 for (QStringList::ConstIterator it = value.begin(); it != value.end(); ++it) {
149 prop[count].length = (*it).length();
150 vl.append((*it).toUtf8());
151 prop[count].value = (char*)vl.constLast().data();
152 ++count;
153 }
154 sm_setProperty(name.toLatin1().data(), SmLISTofARRAY8, count, prop);
155 delete [] prop;
156 }
157
158
159 // workaround for broken libsm, see below
160 struct QT_smcConn {
161 unsigned int save_yourself_in_progress : 1;
162 unsigned int shutdown_in_progress : 1;
163 };
164
sm_saveYourselfCallback(SmcConn smcConn,SmPointer clientData,int saveType,Bool shutdown,int interactStyle,Bool)165 static 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
sm_performSaveYourself(QXcbSessionManager * sm)187 static 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, 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(0);
202
203 // tell the session manager about our program in best POSIX style
204 sm_setProperty(QString::fromLatin1(SmProgram), 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>(sysconf(_SC_GETPW_R_SIZE_MAX), 1024L));
209 struct passwd entry;
210 while (getpwuid_r(geteuid(), &entry, buf.data(), buf.size(), &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(buf.size() * 2);
221 }
222 #else
223 entryPtr = getpwuid(geteuid());
224 #endif
225 if (entryPtr)
226 sm_setProperty(QString::fromLatin1(SmUserID), QString::fromLocal8Bit(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(fi.fileName(), 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(QString::fromLatin1(SmRestartCommand), sm->restartCommand());
275 sm_setProperty(QString::fromLatin1(SmDiscardCommand), 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, 1, &prop);
283
284 // we are done
285 SmcSaveYourselfDone(smcConnection, !sm_cancel);
286 }
287 }
288
sm_dieCallback(SmcConn smcConn,SmPointer)289 static void sm_dieCallback(SmcConn smcConn, SmPointer /* clientData */)
290 {
291 if (smcConn != smcConnection)
292 return;
293 resetSmState();
294 QWindowSystemInterface::handleApplicationTermination<QWindowSystemInterface::SynchronousDelivery>();
295 }
296
sm_shutdownCancelledCallback(SmcConn smcConn,SmPointer clientData)297 static 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
sm_saveCompleteCallback(SmcConn smcConn,SmPointer)306 static void sm_saveCompleteCallback(SmcConn smcConn, SmPointer /*clientData */)
307 {
308 if (smcConn != smcConnection)
309 return;
310 resetSmState();
311 }
312
sm_interactCallback(SmcConn smcConn,SmPointer clientData)313 static void sm_interactCallback(SmcConn smcConn, SmPointer clientData)
314 {
315 if (smcConn != smcConnection)
316 return;
317 if (sm_waitingForInteraction)
318 ((QXcbSessionManager *) clientData)->exitEventLoop();
319 }
320
sm_saveYourselfPhase2Callback(SmcConn smcConn,SmPointer clientData)321 static void sm_saveYourselfPhase2Callback(SmcConn smcConn, SmPointer clientData)
322 {
323 if (smcConn != smcConnection)
324 return;
325 sm_in_phase2 = true;
326 sm_performSaveYourself((QXcbSessionManager *) clientData);
327 }
328
329
socketActivated()330 void QSmSocketReceiver::socketActivated()
331 {
332 IceProcessMessages(SmcGetIceConnection(smcConnection), nullptr, nullptr);
333 }
334
335
336 // QXcbSessionManager starts here
337
QXcbSessionManager(const QString & id,const QString & key)338 QXcbSessionManager::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("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(myId));
373 ::free(myId); // it was allocated by C
374
375 QString error = QString::fromLocal8Bit(cerror);
376 if (!smcConnection)
377 qWarning("Qt: Session management error: %s", qPrintable(error));
378 else
379 sm_receiver = new QSmSocketReceiver(IceConnectionNumber(SmcGetIceConnection(smcConnection)));
380 }
381
~QXcbSessionManager()382 QXcbSessionManager::~QXcbSessionManager()
383 {
384 if (smcConnection)
385 SmcCloseConnection(smcConnection, 0, nullptr);
386 smcConnection = nullptr;
387 delete sm_receiver;
388 }
389
390
handle() const391 void* QXcbSessionManager::handle() const
392 {
393 return (void*) smcConnection;
394 }
395
allowsInteraction()396 bool 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
allowsErrorInteraction()426 bool 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
release()456 void 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
cancel()466 void QXcbSessionManager::cancel()
467 {
468 sm_cancel = true;
469 }
470
setManagerProperty(const QString & name,const QString & value)471 void QXcbSessionManager::setManagerProperty(const QString &name, const QString &value)
472 {
473 sm_setProperty(name, value);
474 }
475
setManagerProperty(const QString & name,const QStringList & value)476 void QXcbSessionManager::setManagerProperty(const QString &name, const QStringList &value)
477 {
478 sm_setProperty(name, value);
479 }
480
isPhase2() const481 bool QXcbSessionManager::isPhase2() const
482 {
483 return sm_in_phase2;
484 }
485
requestPhase2()486 void QXcbSessionManager::requestPhase2()
487 {
488 sm_phase2 = true;
489 }
490
exitEventLoop()491 void QXcbSessionManager::exitEventLoop()
492 {
493 m_eventLoop->exit();
494 }
495
496 #include "qxcbsessionmanager.moc"
497
498 #endif
499