1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 David Faure <david.faure@kdab.com>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the test suite of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU
20** General Public License version 3 as published by the Free Software
21** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include <QtGui>
31#include <QtTest/QtTest>
32
33#include <QApplication>
34#include <QX11Info>
35
36class tst_QX11Info : public QObject
37{
38 Q_OBJECT
39
40private slots:
41 void staticFunctionsBeforeQApplication();
42 void startupId();
43 void isPlatformX11();
44 void appTime();
45 void peeker();
46};
47
48void tst_QX11Info::staticFunctionsBeforeQApplication()
49{
50 QVERIFY(!QApplication::instance());
51
52 // none of these functions should crash if QApplication hasn't
53 // been constructed
54
55 Display *display = QX11Info::display();
56 QCOMPARE(display, (Display *)0);
57
58#if 0
59 const char *appClass = QX11Info::appClass();
60 QCOMPARE(appClass, (const char *)0);
61#endif
62 int appScreen = QX11Info::appScreen();
63 QCOMPARE(appScreen, 0);
64#if 0
65 int appDepth = QX11Info::appDepth();
66 QCOMPARE(appDepth, 32);
67 int appCells = QX11Info::appCells();
68 QCOMPARE(appCells, 0);
69 Qt::HANDLE appColormap = QX11Info::appColormap();
70 QCOMPARE(appColormap, static_cast<Qt::HANDLE>(0));
71 void *appVisual = QX11Info::appVisual();
72 QCOMPARE(appVisual, static_cast<void *>(0));
73#endif
74 unsigned long appRootWindow = QX11Info::appRootWindow();
75 QCOMPARE(appRootWindow, static_cast<unsigned long>(0));
76
77#if 0
78 bool appDefaultColormap = QX11Info::appDefaultColormap();
79 QCOMPARE(appDefaultColormap, true);
80 bool appDefaultVisual = QX11Info::appDefaultVisual();
81 QCOMPARE(appDefaultVisual, true);
82#endif
83
84 int appDpiX = QX11Info::appDpiX();
85 int appDpiY = QX11Info::appDpiY();
86 QCOMPARE(appDpiX, 75);
87 QCOMPARE(appDpiY, 75);
88
89 unsigned long appTime = QX11Info::appTime();
90 unsigned long appUserTime = QX11Info::appUserTime();
91 QCOMPARE(appTime, 0ul);
92 QCOMPARE(appUserTime, 0ul);
93 // setApp*Time do nothing without QApplication
94 QX11Info::setAppTime(1234);
95 QX11Info::setAppUserTime(5678);
96 appTime = QX11Info::appTime();
97 appUserTime = QX11Info::appUserTime();
98 QCOMPARE(appTime, 0ul);
99 QCOMPARE(appTime, 0ul);
100
101 QX11Info::isCompositingManagerRunning();
102}
103
104static const char idFromEnv[] = "startupid_TIME123456";
105void initialize()
106{
107 qputenv(varName: "DESKTOP_STARTUP_ID", value: idFromEnv);
108}
109Q_CONSTRUCTOR_FUNCTION(initialize)
110
111void tst_QX11Info::startupId()
112{
113 int argc = 0;
114 QApplication app(argc, 0);
115
116 // This relies on the fact that no widget was shown yet,
117 // so please make sure this method is always the first test.
118 QCOMPARE(QString(QX11Info::nextStartupId()), QString(idFromEnv));
119 QWidget w;
120 w.show();
121 QVERIFY(QX11Info::nextStartupId().isEmpty());
122
123 QByteArray idSecondWindow = "startupid2_TIME234567";
124 QX11Info::setNextStartupId(idSecondWindow);
125 QCOMPARE(QX11Info::nextStartupId(), idSecondWindow);
126
127 QWidget w2;
128 w2.show();
129 QVERIFY(QX11Info::nextStartupId().isEmpty());
130}
131
132void tst_QX11Info::isPlatformX11()
133{
134 int argc = 0;
135 QApplication app(argc, 0);
136
137 QVERIFY(QX11Info::isPlatformX11());
138}
139
140void tst_QX11Info::appTime()
141{
142 int argc = 0;
143 QApplication app(argc, 0);
144
145 // No X11 event received yet
146 QCOMPARE(QX11Info::appTime(), 0ul);
147 QCOMPARE(QX11Info::appUserTime(), 0ul);
148
149 // Trigger some X11 events
150 QWindow window;
151 window.show();
152 QTest::qWait(ms: 100);
153 QVERIFY(QX11Info::appTime() > 0);
154
155 unsigned long t0 = QX11Info::appTime();
156 unsigned long t1 = t0 + 1;
157 QX11Info::setAppTime(t1);
158 QCOMPARE(QX11Info::appTime(), t1);
159
160 unsigned long u0 = QX11Info::appUserTime();
161 unsigned long u1 = u0 + 1;
162 QX11Info::setAppUserTime(u1);
163 QCOMPARE(QX11Info::appUserTime(), u1);
164}
165
166class PeekerTest : public QWindow
167{
168public:
169 PeekerTest()
170 {
171 setGeometry(posx: 100, posy: 100, w: 400, h: 400);
172 m_peekerFirstId = QX11Info::generatePeekerId();
173 QVERIFY(m_peekerFirstId >= 0);
174 m_peekerSecondId = QX11Info::generatePeekerId();
175 QVERIFY(m_peekerSecondId == m_peekerFirstId + 1);
176 // Get X atoms
177 xcb_connection_t *c = QX11Info::connection();
178 const char *first = "QT_XCB_PEEKER_TEST_FIRST";
179 const char *second = "QT_XCB_PEEKER_TEST_SECOND";
180 xcb_intern_atom_reply_t *reply =
181 xcb_intern_atom_reply(c, cookie: xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: first), name: first), e: 0);
182 QVERIFY2(reply != nullptr, first);
183 m_atomFirst = reply->atom;
184 free(ptr: reply);
185 reply = xcb_intern_atom_reply(c, cookie: xcb_intern_atom(c, only_if_exists: false, name_len: strlen(s: second), name: second), e: 0);
186 QVERIFY2(reply != nullptr, second);
187 m_atomSecond = reply->atom;
188 free(ptr: reply);
189 }
190
191protected:
192 void triggerPropertyNotifyEvents()
193 {
194 xcb_window_t rootWindow = QX11Info::appRootWindow();
195 xcb_connection_t *connection = QX11Info::connection();
196 xcb_change_property(c: connection, mode: XCB_PROP_MODE_APPEND, window: rootWindow, property: m_atomFirst,
197 type: XCB_ATOM_INTEGER, format: 32, data_len: 0, data: nullptr);
198 xcb_change_property(c: connection, mode: XCB_PROP_MODE_APPEND, window: rootWindow, property: m_atomSecond,
199 type: XCB_ATOM_INTEGER, format: 32, data_len: 0, data: nullptr);
200 xcb_flush(c: connection);
201 }
202
203 static bool checkForPropertyNotifyByAtom(xcb_generic_event_t *event, void *data)
204 {
205 bool isPropertyNotify = (event->response_type & ~0x80) == XCB_PROPERTY_NOTIFY;
206 if (isPropertyNotify) {
207 xcb_property_notify_event_t *pn =
208 reinterpret_cast<xcb_property_notify_event_t *>(event);
209 xcb_atom_t *atom = static_cast<xcb_atom_t *>(data);
210 if (pn->atom == *atom)
211 return true;
212 }
213 return false;
214 }
215
216 bool sanityCheckPeeking(xcb_generic_event_t *event)
217 {
218 m_countWithCaching++;
219 bool isPropertyNotify = (event->response_type & ~0x80) == XCB_PROPERTY_NOTIFY;
220 if (isPropertyNotify) {
221 xcb_property_notify_event_t *pn =
222 reinterpret_cast<xcb_property_notify_event_t *>(event);
223 if (pn->atom == m_atomFirst) {
224 if (m_indexFirst == -1) {
225 m_indexFirst = m_countWithCaching;
226 // continue peeking, maybe the second event is already in the queue
227 return false;
228 }
229 m_foundFirstEventAgain = true;
230 // Return so we can fail the test with QVERIFY2, for more details
231 // see QTBUG-62354
232 return true;
233 }
234 // Let it peek to the end, even when the second event was found
235 if (pn->atom == m_atomSecond) {
236 m_indexSecond = m_countWithCaching;
237 if (m_indexFirst == -1) {
238 m_foundSecondBeforeFirst = true;
239 // Same as above, see QTBUG-62354
240 return true;
241 }
242 }
243 }
244 return false;
245 }
246
247 void exposeEvent(QExposeEvent *)
248 {
249 if (m_ignoreSubsequentExpose)
250 return;
251 // We don't want to execute this handler again in case there are more expose
252 // events after calling QCoreApplication::processEvents() later in this test
253 m_ignoreSubsequentExpose = true;
254
255 xcb_flush(c: QX11Info::connection());
256 bool found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomFirst);
257 QVERIFY2(!found, "Found m_atomFirst which should not be in the queue yet");
258 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomSecond);
259 QVERIFY2(!found, "Found m_atomSecond which should not be in the queue yet");
260
261 triggerPropertyNotifyEvents();
262
263 bool earlyExit = false;
264 while (!earlyExit && (m_indexFirst == -1 || m_indexSecond == -1)) {
265
266 earlyExit = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *event, void *data) {
267 return static_cast<PeekerTest *>(data)->sanityCheckPeeking(event);
268 }, peekerData: this, option: QX11Info::PeekFromCachedIndex, peekerId: m_peekerFirstId);
269
270 if (m_countWithCaching == -1)
271 QVERIFY2(!earlyExit, "Unexpected return value for an empty queue");
272 }
273
274 QVERIFY2(!m_foundFirstEventAgain,
275 "Found the same notify event twice, maybe broken index cache?");
276 QVERIFY2(!m_foundSecondBeforeFirst, "Found second event before first");
277 QVERIFY2(!earlyExit,
278 "Unexpected return value for a peeker that always scans to the end of the queue");
279
280 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomFirst,
281 option: QX11Info::PeekDefault, peekerId: m_peekerFirstId);
282 QVERIFY2(found, "Failed to find m_atomFirst, when peeking from start and ignoring "
283 "the cached index associated with the passed in peeker id");
284 // The above call updated index cache for m_peekerFirstId to the position of
285 // event(m_atomFirst) + 1
286 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomSecond,
287 option: QX11Info::PeekFromCachedIndex, peekerId: m_peekerFirstId);
288 QVERIFY2(found, "Unexpectedly failed to find m_atomSecond");
289
290 QVERIFY(m_indexFirst <= m_countWithCaching);
291 QVERIFY(m_indexSecond <= m_countWithCaching);
292 QVERIFY(m_indexFirst < m_indexSecond);
293 QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *data) {
294 static_cast<PeekerTest *>(data)->m_countFromStart++;
295 return false;
296 }, peekerData: this);
297 QVERIFY(m_countWithCaching <= m_countFromStart);
298 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomFirst,
299 option: QX11Info::PeekFromCachedIndex, peekerId: m_peekerSecondId);
300 QVERIFY2(found, "m_peekerSecondId failed to find the event");
301
302 // Remove peeker id from within the peeker while using it for peeking
303 QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *data) {
304 PeekerTest *obj = static_cast<PeekerTest *>(data);
305 QX11Info::removePeekerId(peekerId: obj->m_peekerSecondId);
306 return true;
307 }, peekerData: this, option: QX11Info::PeekFromCachedIndex, peekerId: m_peekerSecondId);
308 // Check that it really has been removed from the cache
309 bool ok = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *) {
310 return true;
311 }, peekerData: nullptr, option: QX11Info::PeekFromCachedIndex, peekerId: m_peekerSecondId);
312 QVERIFY2(!ok, "Unexpected return value when attempting to peek from cached "
313 "index when peeker id has been removed from the cache");
314
315 // Sanity check other input combinations
316 QVERIFY(!QX11Info::removePeekerId(-99));
317 ok = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *) {
318 return true;
319 }, peekerData: nullptr, option: QX11Info::PeekFromCachedIndex, peekerId: -100);
320 QVERIFY2(!ok, "PeekFromCachedIndex with invalid peeker id unexpectedly succeeded");
321 ok = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *) {
322 return true;
323 }, peekerData: nullptr, option: QX11Info::PeekDefault, peekerId: -100);
324 QVERIFY2(!ok, "PeekDefault with invalid peeker id unexpectedly succeeded");
325 ok = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *) {
326 return true;
327 }, peekerData: nullptr, option: QX11Info::PeekFromCachedIndex);
328 QVERIFY2(!ok, "PeekFromCachedIndex without peeker id unexpectedly succeeded");
329 ok = QX11Info::peekEventQueue(peeker: [](xcb_generic_event_t *, void *) {
330 return true;
331 }, peekerData: nullptr, option: QX11Info::PeekDefault);
332 QVERIFY2(ok, "PeekDefault without peeker id unexpectedly failed");
333
334 QCoreApplication::processEvents(); // Flush buffered events
335
336 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomFirst);
337 QVERIFY2(!found, "Found m_atomFirst in the queue after flushing");
338 found = QX11Info::peekEventQueue(peeker: checkForPropertyNotifyByAtom, peekerData: &m_atomSecond);
339 QVERIFY2(!found, "Found m_atomSecond in the queue after flushing");
340
341 QVERIFY(QX11Info::removePeekerId(m_peekerFirstId));
342 QVERIFY2(!QX11Info::removePeekerId(m_peekerFirstId),
343 "Removing the same peeker id twice unexpectedly succeeded");
344#if 0
345 qDebug() << "Buffered event queue size (caching) : " << m_countWithCaching + 1;
346 qDebug() << "Buffered event queue size (from start) : " << m_countFromStart + 1;
347 qDebug() << "PropertyNotify[FIRST] at : " << m_indexFirst;
348 qDebug() << "PropertyNotify[SECOND] at : " << m_indexSecond;
349#endif
350 }
351
352private:
353 xcb_atom_t m_atomFirst = -1;
354 xcb_atom_t m_atomSecond = -1;
355 qint32 m_peekerFirstId = -1;
356 qint32 m_peekerSecondId = -1;
357 qint32 m_indexFirst = -1;
358 qint32 m_indexSecond = -1;
359 bool m_foundFirstEventAgain = false;
360 qint32 m_countWithCaching = -1;
361 qint32 m_countFromStart = -1;
362 bool m_ignoreSubsequentExpose = false;
363 bool m_foundSecondBeforeFirst = false;
364};
365
366void tst_QX11Info::peeker()
367{
368 int argc = 0;
369 QGuiApplication app(argc, 0);
370
371 PeekerTest test;
372 test.show();
373
374 QVERIFY(QTest::qWaitForWindowExposed(&test));
375}
376
377QTEST_APPLESS_MAIN(tst_QX11Info)
378
379#include "tst_qx11info.moc"
380

source code of qtx11extras/tests/auto/qx11info/tst_qx11info.cpp