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] = &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 
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