1 /*
2     SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.1-or-later
5 */
6 
7 #include "nettesthelper.h"
8 #include <QProcess>
9 #include <netwm.h>
10 #include <qtest_widgets.h>
11 // system
12 #include <unistd.h>
13 
14 class Property : public QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter>
15 {
16 public:
Property(xcb_get_property_reply_t * p=nullptr)17     Property(xcb_get_property_reply_t *p = nullptr)
18         : QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter>(p)
19     {
20     }
21 };
22 
23 Q_DECLARE_METATYPE(NET::Orientation)
24 Q_DECLARE_METATYPE(NET::DesktopLayoutCorner)
25 
26 static const char *s_wmName = "netrootinfotest";
27 
28 class NetRootInfoTestWM : public QObject
29 {
30     Q_OBJECT
31 private Q_SLOTS:
32     void initTestCase();
33     void cleanupTestCase();
34     void init();
35     void cleanup();
36 
37     void testCtor();
38     void testSupported();
39     void testClientList();
40     void testClientListStacking();
41     void testNumberOfDesktops();
42     void testCurrentDesktop();
43     void testDesktopNames();
44     void testDesktopLayout_data();
45     void testDesktopLayout();
46     void testDesktopGeometry();
47     void testDesktopViewports();
48     void testShowingDesktop_data();
49     void testShowingDesktop();
50     void testWorkArea();
51     void testActiveWindow();
52     void testVirtualRoots();
53     void testDontCrashMapViewports();
54 
55 private:
56     void waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2 = NET::Property2(0));
connection()57     xcb_connection_t *connection()
58     {
59         return m_connection;
60     }
61     xcb_connection_t *m_connection;
62     QVector<xcb_connection_t *> m_connections;
63     QScopedPointer<QProcess> m_xvfb;
64     xcb_window_t m_supportWindow;
65     xcb_window_t m_rootWindow;
66 };
67 
cleanupTestCase()68 void NetRootInfoTestWM::cleanupTestCase()
69 {
70     while (!m_connections.isEmpty()) {
71         xcb_disconnect(m_connections.takeFirst());
72     }
73 }
74 
initTestCase()75 void NetRootInfoTestWM::initTestCase()
76 {
77 }
78 
init()79 void NetRootInfoTestWM::init()
80 {
81     // first reset just to be sure
82     m_connection = nullptr;
83     m_supportWindow = XCB_WINDOW_NONE;
84     // start Xvfb
85     m_xvfb.reset(new QProcess);
86     // use pipe to pass fd to Xvfb to get back the display id
87     int pipeFds[2];
88     QVERIFY(pipe(pipeFds) == 0);
89     m_xvfb->start(QStringLiteral("Xvfb"), QStringList{QStringLiteral("-displayfd"), QString::number(pipeFds[1])});
90     QVERIFY(m_xvfb->waitForStarted());
91     QCOMPARE(m_xvfb->state(), QProcess::Running);
92 
93     // reads from pipe, closes write side
94     close(pipeFds[1]);
95 
96     QFile readPipe;
97     QVERIFY(readPipe.open(pipeFds[0], QIODevice::ReadOnly, QFileDevice::AutoCloseHandle));
98     QByteArray displayNumber = readPipe.readLine();
99     readPipe.close();
100 
101     displayNumber.prepend(QByteArray(":"));
102     displayNumber.remove(displayNumber.size() - 1, 1);
103 
104     // create X connection
105     int screen = 0;
106     m_connection = xcb_connect(displayNumber.constData(), &screen);
107     QVERIFY(m_connection);
108     QVERIFY(!xcb_connection_has_error(m_connection));
109     m_rootWindow = KXUtils::rootWindow(m_connection, screen);
110     uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
111     xcb_change_window_attributes(m_connection, m_rootWindow, XCB_CW_EVENT_MASK, values);
112 
113     // create support window
114     values[0] = true;
115     m_supportWindow = xcb_generate_id(m_connection);
116     xcb_create_window(m_connection,
117                       XCB_COPY_FROM_PARENT,
118                       m_supportWindow,
119                       m_rootWindow,
120                       0,
121                       0,
122                       1,
123                       1,
124                       0,
125                       XCB_COPY_FROM_PARENT,
126                       XCB_COPY_FROM_PARENT,
127                       XCB_CW_OVERRIDE_REDIRECT,
128                       values);
129     const uint32_t lowerValues[] = {XCB_STACK_MODE_BELOW};
130     // we need to do the lower window with a roundtrip, otherwise NETRootInfo is not functioning
131     QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> error(
132         xcb_request_check(m_connection, xcb_configure_window_checked(m_connection, m_supportWindow, XCB_CONFIG_WINDOW_STACK_MODE, lowerValues)));
133     QVERIFY(error.isNull());
134 }
135 
cleanup()136 void NetRootInfoTestWM::cleanup()
137 {
138     // destroy support window
139     xcb_destroy_window(connection(), m_supportWindow);
140     m_supportWindow = XCB_WINDOW_NONE;
141 
142     // close connection
143     // delay till clenupTestCase as otherwise xcb reuses the same memory address
144     m_connections << connection();
145     // kill Xvfb
146     m_xvfb->terminate();
147     m_xvfb->waitForFinished();
148     m_xvfb.reset();
149 }
150 
waitForPropertyChange(NETRootInfo * info,xcb_atom_t atom,NET::Property prop,NET::Property2 prop2)151 void NetRootInfoTestWM::waitForPropertyChange(NETRootInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2)
152 {
153     while (true) {
154         KXUtils::ScopedCPointer<xcb_generic_event_t> event(xcb_wait_for_event(connection()));
155         if (event.isNull()) {
156             break;
157         }
158         if ((event->response_type & ~0x80) != XCB_PROPERTY_NOTIFY) {
159             continue;
160         }
161         xcb_property_notify_event_t *pe = reinterpret_cast<xcb_property_notify_event_t *>(event.data());
162         if (pe->window != m_rootWindow) {
163             continue;
164         }
165         if (pe->atom != atom) {
166             continue;
167         }
168         NET::Properties dirty;
169         NET::Properties2 dirty2;
170         info->event(event.data(), &dirty, &dirty2);
171         if (prop != 0) {
172             QVERIFY(dirty & prop);
173         }
174         if (prop2 != 0) {
175             QVERIFY(dirty2 & prop2);
176         }
177         if (!prop) {
178             QCOMPARE(dirty, NET::Properties());
179         }
180         if (!prop2) {
181             QCOMPARE(dirty2, NET::Properties2());
182         }
183         break;
184     }
185 }
186 
testCtor()187 void NetRootInfoTestWM::testCtor()
188 {
189     QVERIFY(connection());
190     NETRootInfo
191         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
192     QCOMPARE(rootInfo.xcbConnection(), connection());
193     QCOMPARE(rootInfo.rootWindow(), m_rootWindow);
194     QCOMPARE(rootInfo.supportWindow(), m_supportWindow);
195     QCOMPARE(rootInfo.wmName(), s_wmName);
196     QCOMPARE(rootInfo.supportedProperties(), NET::WMAllProperties);
197     QCOMPARE(rootInfo.supportedProperties2(), NET::WM2AllProperties);
198     QCOMPARE(rootInfo.supportedActions(), NET::Actions(~0u));
199     QCOMPARE(rootInfo.supportedStates(), NET::States(~0u));
200     QCOMPARE(rootInfo.supportedWindowTypes(), NET::AllTypesMask);
201 
202     QCOMPARE(rootInfo.passedProperties(), NET::WMAllProperties);
203     QCOMPARE(rootInfo.passedProperties2(), NET::WM2AllProperties);
204     QCOMPARE(rootInfo.passedActions(), NET::Actions(~0u));
205     QCOMPARE(rootInfo.passedStates(), NET::States(~0u));
206     QCOMPARE(rootInfo.passedWindowTypes(), NET::AllTypesMask);
207 }
208 
testSupported()209 void NetRootInfoTestWM::testSupported()
210 {
211     KXUtils::Atom supported(connection(), QByteArrayLiteral("_NET_SUPPORTED"));
212     KXUtils::Atom wmCheck(connection(), QByteArrayLiteral("_NET_SUPPORTING_WM_CHECK"));
213     KXUtils::Atom wmName(connection(), QByteArrayLiteral("_NET_WM_NAME"));
214     KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
215     QVERIFY(connection());
216     NETRootInfo
217         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
218     int count = 0;
219     for (int i = 0; i < 33; ++i) {
220         if (i == 12) {
221             continue;
222         }
223         QVERIFY(rootInfo.isSupported(NET::Property(1 << i)));
224         count++;
225     }
226     for (int i = 0; i < 22; ++i) {
227         QVERIFY(rootInfo.isSupported(NET::Property2(1 << i)));
228         count++;
229     }
230     for (int i = 0; i < 17; ++i) {
231         QVERIFY(rootInfo.isSupported(NET::WindowTypeMask(1 << i)));
232         count++;
233     }
234     for (int i = 0; i < 13; ++i) {
235         QVERIFY(rootInfo.isSupported(NET::State(1 << i)));
236         count++;
237     }
238     for (int i = 0; i < 10; ++i) {
239         QVERIFY(rootInfo.isSupported(NET::Action(1 << i)));
240         count++;
241     }
242     // NET::WMFrameExtents has two properties
243     count += 1;
244     // XAWState, WMGeometry, WM2TransientFor, WM2GroupLeader, WM2WindowClass, WM2WindowRole, WM2ClientMachine
245     count -= 7;
246     // WM2BlockCompositing has 3 properties
247     count += 2;
248     // Add _GTK_FRAME_EXTENTS
249     ++count;
250 
251     QVERIFY(supported != XCB_ATOM_NONE);
252     QVERIFY(utf8String != XCB_ATOM_NONE);
253     QVERIFY(wmCheck != XCB_ATOM_NONE);
254     QVERIFY(wmName != XCB_ATOM_NONE);
255 
256     // we should have got some events
257     waitForPropertyChange(&rootInfo, supported, NET::Supported);
258     waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck);
259 
260     // get the cookies of the things to check
261     xcb_get_property_cookie_t supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 101);
262     xcb_get_property_cookie_t wmCheckRootCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), wmCheck, XCB_ATOM_WINDOW, 0, 1);
263     xcb_get_property_cookie_t wmCheckSupportWinCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmCheck, XCB_ATOM_WINDOW, 0, 1);
264     xcb_get_property_cookie_t wmNameCookie = xcb_get_property_unchecked(connection(), false, m_supportWindow, wmName, utf8String, 0, 16);
265 
266     Property supportedReply(xcb_get_property_reply(connection(), supportedCookie, nullptr));
267     QVERIFY(!supportedReply.isNull());
268     QCOMPARE(supportedReply->format, uint8_t(32));
269     QCOMPARE(supportedReply->value_len, uint32_t(count));
270     // TODO: check that the correct atoms are set?
271     Property wmCheckRootReply(xcb_get_property_reply(connection(), wmCheckRootCookie, nullptr));
272     QVERIFY(!wmCheckRootReply.isNull());
273     QCOMPARE(wmCheckRootReply->format, uint8_t(32));
274     QCOMPARE(wmCheckRootReply->value_len, uint32_t(1));
275     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(wmCheckRootReply.data()))[0], m_supportWindow);
276 
277     Property wmCheckSupportReply(xcb_get_property_reply(connection(), wmCheckSupportWinCookie, nullptr));
278     QVERIFY(!wmCheckSupportReply.isNull());
279     QCOMPARE(wmCheckSupportReply->format, uint8_t(32));
280     QCOMPARE(wmCheckSupportReply->value_len, uint32_t(1));
281     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(wmCheckSupportReply.data()))[0], m_supportWindow);
282 
283     Property wmNameReply(xcb_get_property_reply(connection(), wmNameCookie, nullptr));
284     QVERIFY(!wmNameReply.isNull());
285     QCOMPARE(wmNameReply->format, uint8_t(8));
286     QCOMPARE(wmNameReply->value_len, uint32_t(15));
287     QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(wmNameReply.data())), s_wmName);
288 
289     // disable some supported
290     rootInfo.setSupported(NET::WMFrameExtents, false);
291     rootInfo.setSupported(NET::WM2KDETemporaryRules, false);
292     rootInfo.setSupported(NET::ActionChangeDesktop, false);
293     rootInfo.setSupported(NET::FullScreen, false);
294     QVERIFY(rootInfo.isSupported(NET::ToolbarMask));
295     QVERIFY(rootInfo.isSupported(NET::OnScreenDisplayMask));
296     QVERIFY(rootInfo.isSupported(NET::DockMask));
297     rootInfo.setSupported(NET::ToolbarMask, false);
298     rootInfo.setSupported(NET::OnScreenDisplayMask, false);
299 
300     QVERIFY(!rootInfo.isSupported(NET::WMFrameExtents));
301     QVERIFY(!rootInfo.isSupported(NET::WM2KDETemporaryRules));
302     QVERIFY(!rootInfo.isSupported(NET::ActionChangeDesktop));
303     QVERIFY(!rootInfo.isSupported(NET::FullScreen));
304     QVERIFY(!rootInfo.isSupported(NET::ToolbarMask));
305     QVERIFY(!rootInfo.isSupported(NET::OnScreenDisplayMask));
306     QVERIFY(rootInfo.isSupported(NET::DockMask));
307 
308     // lets get supported again
309     supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90);
310     supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr));
311     QVERIFY(!supportedReply.isNull());
312     QCOMPARE(supportedReply->format, uint8_t(32));
313     QCOMPARE(supportedReply->value_len, uint32_t(count - 7));
314 
315     for (int i = 0; i < 5; ++i) {
316         // we should have got some events
317         waitForPropertyChange(&rootInfo, supported, NET::Supported);
318         waitForPropertyChange(&rootInfo, wmCheck, NET::SupportingWMCheck);
319     }
320 
321     // turn something off, just to get another event
322     rootInfo.setSupported(NET::WM2BlockCompositing, false);
323     // lets get supported again
324     supportedCookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), supported, XCB_ATOM_ATOM, 0, 90);
325     supportedReply.reset(xcb_get_property_reply(connection(), supportedCookie, nullptr));
326     QVERIFY(!supportedReply.isNull());
327     QCOMPARE(supportedReply->format, uint8_t(32));
328     QCOMPARE(supportedReply->value_len, uint32_t(count - 9));
329     NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck);
330     waitForPropertyChange(&clientInfo, supported, NET::Supported);
331     waitForPropertyChange(&clientInfo, wmCheck, NET::SupportingWMCheck);
332 }
333 
testClientList()334 void NetRootInfoTestWM::testClientList()
335 {
336     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST"));
337     QVERIFY(connection());
338     NETRootInfo
339         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
340     QCOMPARE(rootInfo.clientListCount(), 0);
341     QVERIFY(!rootInfo.clientList());
342 
343     xcb_window_t windows[] = {xcb_generate_id(connection()),
344                               xcb_generate_id(connection()),
345                               xcb_generate_id(connection()),
346                               xcb_generate_id(connection()),
347                               xcb_generate_id(connection())};
348     rootInfo.setClientList(windows, 5);
349     QCOMPARE(rootInfo.clientListCount(), 5);
350     const xcb_window_t *otherWins = rootInfo.clientList();
351     for (int i = 0; i < 5; ++i) {
352         QCOMPARE(otherWins[i], windows[i]);
353     }
354 
355     // compare with the X property
356     QVERIFY(atom != XCB_ATOM_NONE);
357     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
358     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
359     QVERIFY(!reply.isNull());
360     QCOMPARE(reply->format, uint8_t(32));
361     QCOMPARE(reply->value_len, uint32_t(5));
362     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.data()));
363     for (int i = 0; i < 5; ++i) {
364         QCOMPARE(propWins[i], windows[i]);
365     }
366 
367     // wait for our property
368     NETRootInfo clientInfo(connection(), NET::Supported | NET::SupportingWMCheck | NET::ClientList);
369     waitForPropertyChange(&clientInfo, atom, NET::ClientList);
370     QCOMPARE(clientInfo.clientListCount(), 5);
371     const xcb_window_t *otherWins2 = clientInfo.clientList();
372     for (int i = 0; i < 5; ++i) {
373         QCOMPARE(otherWins2[i], windows[i]);
374     }
375 }
376 
testClientListStacking()377 void NetRootInfoTestWM::testClientListStacking()
378 {
379     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CLIENT_LIST_STACKING"));
380     QVERIFY(connection());
381     NETRootInfo
382         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
383     QCOMPARE(rootInfo.clientListStackingCount(), 0);
384     QVERIFY(!rootInfo.clientListStacking());
385 
386     xcb_window_t windows[] = {xcb_generate_id(connection()),
387                               xcb_generate_id(connection()),
388                               xcb_generate_id(connection()),
389                               xcb_generate_id(connection()),
390                               xcb_generate_id(connection())};
391     rootInfo.setClientListStacking(windows, 5);
392     QCOMPARE(rootInfo.clientListStackingCount(), 5);
393     const xcb_window_t *otherWins = rootInfo.clientListStacking();
394     for (int i = 0; i < 5; ++i) {
395         QCOMPARE(otherWins[i], windows[i]);
396     }
397 
398     // compare with the X property
399     QVERIFY(atom != XCB_ATOM_NONE);
400     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
401     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
402     QVERIFY(!reply.isNull());
403     QCOMPARE(reply->format, uint8_t(32));
404     QCOMPARE(reply->value_len, uint32_t(5));
405     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.data()));
406     for (int i = 0; i < 5; ++i) {
407         QCOMPARE(propWins[i], windows[i]);
408     }
409 
410     // wait for our property
411     waitForPropertyChange(&rootInfo, atom, NET::ClientListStacking);
412     QCOMPARE(rootInfo.clientListStackingCount(), 5);
413     const xcb_window_t *otherWins2 = rootInfo.clientListStacking();
414     for (int i = 0; i < 5; ++i) {
415         QCOMPARE(otherWins2[i], windows[i]);
416     }
417 }
418 
testVirtualRoots()419 void NetRootInfoTestWM::testVirtualRoots()
420 {
421     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_VIRTUAL_ROOTS"));
422     QVERIFY(connection());
423     NETRootInfo
424         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
425     QCOMPARE(rootInfo.virtualRootsCount(), 0);
426     QVERIFY(!rootInfo.virtualRoots());
427 
428     xcb_window_t windows[] = {xcb_generate_id(connection()),
429                               xcb_generate_id(connection()),
430                               xcb_generate_id(connection()),
431                               xcb_generate_id(connection()),
432                               xcb_generate_id(connection())};
433     rootInfo.setVirtualRoots(windows, 5);
434     QCOMPARE(rootInfo.virtualRootsCount(), 5);
435     const xcb_window_t *otherWins = rootInfo.virtualRoots();
436     for (int i = 0; i < 5; ++i) {
437         QCOMPARE(otherWins[i], windows[i]);
438     }
439 
440     // compare with the X property
441     QVERIFY(atom != XCB_ATOM_NONE);
442     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 5);
443     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
444     QVERIFY(!reply.isNull());
445     QCOMPARE(reply->format, uint8_t(32));
446     QCOMPARE(reply->value_len, uint32_t(5));
447     const xcb_window_t *propWins = reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.data()));
448     for (int i = 0; i < 5; ++i) {
449         QCOMPARE(propWins[i], windows[i]);
450     }
451 
452     // wait for our property - reported to a Client NETRootInfo
453     NETRootInfo clientInfo(connection(), NET::VirtualRoots);
454     waitForPropertyChange(&clientInfo, atom, NET::VirtualRoots);
455     QCOMPARE(rootInfo.virtualRootsCount(), 5);
456     const xcb_window_t *otherWins2 = rootInfo.virtualRoots();
457     for (int i = 0; i < 5; ++i) {
458         QCOMPARE(otherWins2[i], windows[i]);
459     }
460 }
461 
testNumberOfDesktops()462 void NetRootInfoTestWM::testNumberOfDesktops()
463 {
464     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_NUMBER_OF_DESKTOPS"));
465     QVERIFY(connection());
466     NETRootInfo
467         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
468     QCOMPARE(rootInfo.numberOfDesktops(), 1);
469     rootInfo.setNumberOfDesktops(4);
470     QCOMPARE(rootInfo.numberOfDesktops(), 4);
471 
472     // compare with the X property
473     QVERIFY(atom != XCB_ATOM_NONE);
474     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
475     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
476     QVERIFY(!reply.isNull());
477     QCOMPARE(reply->format, uint8_t(32));
478     QCOMPARE(reply->value_len, uint32_t(1));
479     QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()))[0], uint32_t(4));
480 
481     // wait for our property
482     waitForPropertyChange(&rootInfo, atom, NET::NumberOfDesktops);
483     QCOMPARE(rootInfo.numberOfDesktops(), 4);
484 }
485 
testCurrentDesktop()486 void NetRootInfoTestWM::testCurrentDesktop()
487 {
488     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_CURRENT_DESKTOP"));
489     // TODO: verify that current desktop cannot be higher than number of desktops
490     QVERIFY(connection());
491     NETRootInfo
492         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
493     QCOMPARE(rootInfo.currentDesktop(), 1);
494     rootInfo.setCurrentDesktop(5);
495     QCOMPARE(rootInfo.currentDesktop(), 5);
496 
497     // compare with the X property
498     QVERIFY(atom != XCB_ATOM_NONE);
499     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
500     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
501     QVERIFY(!reply.isNull());
502     QCOMPARE(reply->format, uint8_t(32));
503     QCOMPARE(reply->value_len, uint32_t(1));
504     // note: API starts counting at 1, but property starts counting at 5, because of that subtracting one
505     QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()))[0], uint32_t(5 - 1));
506 
507     // wait for our property
508     waitForPropertyChange(&rootInfo, atom, NET::CurrentDesktop);
509     QCOMPARE(rootInfo.currentDesktop(), 5);
510 }
511 
testDesktopNames()512 void NetRootInfoTestWM::testDesktopNames()
513 {
514     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_NAMES"));
515     KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
516     QVERIFY(connection());
517     NETRootInfo
518         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
519     QVERIFY(!rootInfo.desktopName(0));
520     QVERIFY(!rootInfo.desktopName(1));
521     QVERIFY(!rootInfo.desktopName(2));
522     rootInfo.setDesktopName(1, "foo");
523     rootInfo.setDesktopName(2, "bar");
524     rootInfo.setNumberOfDesktops(2);
525     QCOMPARE(rootInfo.desktopName(1), "foo");
526     QCOMPARE(rootInfo.desktopName(2), "bar");
527 
528     // compare with the X property
529     QVERIFY(atom != XCB_ATOM_NONE);
530     QVERIFY(utf8String != XCB_ATOM_NONE);
531     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, utf8String, 0, 10000);
532     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
533     QVERIFY(!reply.isNull());
534     QCOMPARE(reply->format, uint8_t(8));
535     QCOMPARE(reply->value_len, uint32_t(8));
536     QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.data())), "foo\0bar");
537 
538     // wait for our property
539     waitForPropertyChange(&rootInfo, atom, NET::DesktopNames);
540     QCOMPARE(rootInfo.desktopName(1), "foo");
541     QCOMPARE(rootInfo.desktopName(2), "bar");
542     // there should be two events
543     waitForPropertyChange(&rootInfo, atom, NET::DesktopNames);
544     QCOMPARE(rootInfo.desktopName(1), "foo");
545     QCOMPARE(rootInfo.desktopName(2), "bar");
546 }
547 
testActiveWindow()548 void NetRootInfoTestWM::testActiveWindow()
549 {
550     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_ACTIVE_WINDOW"));
551     QVERIFY(connection());
552     NETRootInfo
553         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
554     QVERIFY(rootInfo.activeWindow() == XCB_WINDOW_NONE);
555     // rootinfo doesn't verify whether our window is a window, so we just generate an ID
556     xcb_window_t activeWindow = xcb_generate_id(connection());
557     rootInfo.setActiveWindow(activeWindow);
558     QCOMPARE(rootInfo.activeWindow(), activeWindow);
559 
560     // compare with the X property
561     QVERIFY(atom != XCB_ATOM_NONE);
562     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_WINDOW, 0, 1);
563     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
564     QVERIFY(!reply.isNull());
565     QCOMPARE(reply->format, uint8_t(32));
566     QCOMPARE(reply->value_len, uint32_t(1));
567     QCOMPARE(reinterpret_cast<xcb_window_t *>(xcb_get_property_value(reply.data()))[0], activeWindow);
568 
569     // wait for our property
570     waitForPropertyChange(&rootInfo, atom, NET::ActiveWindow);
571     QCOMPARE(rootInfo.activeWindow(), activeWindow);
572 }
573 
testDesktopGeometry()574 void NetRootInfoTestWM::testDesktopGeometry()
575 {
576     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_GEOMETRY"));
577     QVERIFY(connection());
578     NETRootInfo
579         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
580     QCOMPARE(rootInfo.desktopGeometry().width, 0);
581     QCOMPARE(rootInfo.desktopGeometry().height, 0);
582 
583     NETSize size;
584     size.width = 1000;
585     size.height = 800;
586     rootInfo.setDesktopGeometry(size);
587     QCOMPARE(rootInfo.desktopGeometry().width, size.width);
588     QCOMPARE(rootInfo.desktopGeometry().height, size.height);
589 
590     // compare with the X property
591     QVERIFY(atom != XCB_ATOM_NONE);
592     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 2);
593     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
594     QVERIFY(!reply.isNull());
595     QCOMPARE(reply->format, uint8_t(32));
596     QCOMPARE(reply->value_len, uint32_t(2));
597     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
598     QCOMPARE(data[0], uint32_t(size.width));
599     QCOMPARE(data[1], uint32_t(size.height));
600 
601     // wait for our property
602     waitForPropertyChange(&rootInfo, atom, NET::DesktopGeometry);
603     QCOMPARE(rootInfo.desktopGeometry().width, size.width);
604     QCOMPARE(rootInfo.desktopGeometry().height, size.height);
605 }
606 
testDesktopLayout_data()607 void NetRootInfoTestWM::testDesktopLayout_data()
608 {
609     QTest::addColumn<NET::Orientation>("orientation");
610     QTest::addColumn<QSize>("columnsRows");
611     QTest::addColumn<NET::DesktopLayoutCorner>("corner");
612 
613     QTest::newRow("h/1/1/tl") << NET::OrientationHorizontal << QSize(1, 1) << NET::DesktopLayoutCornerTopLeft;
614     QTest::newRow("h/1/0/tr") << NET::OrientationHorizontal << QSize(1, 0) << NET::DesktopLayoutCornerTopRight;
615     QTest::newRow("h/0/1/bl") << NET::OrientationHorizontal << QSize(0, 1) << NET::DesktopLayoutCornerBottomLeft;
616     QTest::newRow("h/1/2/br") << NET::OrientationHorizontal << QSize(1, 2) << NET::DesktopLayoutCornerBottomRight;
617     QTest::newRow("v/3/2/tl") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerTopLeft;
618     QTest::newRow("v/5/4/tr") << NET::OrientationVertical << QSize(5, 4) << NET::DesktopLayoutCornerTopRight;
619     QTest::newRow("v/2/1/bl") << NET::OrientationVertical << QSize(2, 1) << NET::DesktopLayoutCornerBottomLeft;
620     QTest::newRow("v/3/2/br") << NET::OrientationVertical << QSize(3, 2) << NET::DesktopLayoutCornerBottomRight;
621 }
622 
testDesktopLayout()623 void NetRootInfoTestWM::testDesktopLayout()
624 {
625     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_LAYOUT"));
626     QVERIFY(connection());
627     NETRootInfo
628         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
629     QFETCH(NET::Orientation, orientation);
630     QFETCH(QSize, columnsRows);
631     QFETCH(NET::DesktopLayoutCorner, corner);
632 
633     rootInfo.setDesktopLayout(orientation, columnsRows.width(), columnsRows.height(), corner);
634     QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation);
635     QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows);
636     QCOMPARE(rootInfo.desktopLayoutCorner(), corner);
637 
638     // compare with the X property
639     QVERIFY(atom != XCB_ATOM_NONE);
640     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 4);
641     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
642     QVERIFY(!reply.isNull());
643     QCOMPARE(reply->format, uint8_t(32));
644     QCOMPARE(reply->value_len, uint32_t(4));
645     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
646     QCOMPARE(data[0], uint32_t(orientation));
647     QCOMPARE(data[1], uint32_t(columnsRows.width()));
648     QCOMPARE(data[2], uint32_t(columnsRows.height()));
649     QCOMPARE(data[3], uint32_t(corner));
650 
651     // wait for our property
652     waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2DesktopLayout);
653     QCOMPARE(rootInfo.desktopLayoutOrientation(), orientation);
654     QCOMPARE(rootInfo.desktopLayoutColumnsRows(), columnsRows);
655     QCOMPARE(rootInfo.desktopLayoutCorner(), corner);
656 
657     NETRootInfo info2(connection(), NET::WMAllProperties, NET::WM2AllProperties);
658     QCOMPARE(info2.desktopLayoutColumnsRows(), columnsRows);
659 }
660 
testDesktopViewports()661 void NetRootInfoTestWM::testDesktopViewports()
662 {
663     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_DESKTOP_VIEWPORT"));
664     QVERIFY(connection());
665     NETRootInfo
666         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
667     // we need to know the number of desktops, therefore setting it
668     rootInfo.setNumberOfDesktops(4);
669     NETPoint desktopOne;
670     desktopOne.x = 100;
671     desktopOne.y = 50;
672     NETPoint desktopTwo;
673     desktopTwo.x = 200;
674     desktopTwo.y = 100;
675     rootInfo.setDesktopViewport(1, desktopOne);
676     rootInfo.setDesktopViewport(2, desktopTwo);
677 
678     const NETPoint compareZero = rootInfo.desktopViewport(0);
679     QCOMPARE(compareZero.x, 0);
680     QCOMPARE(compareZero.y, 0);
681     const NETPoint compareOne = rootInfo.desktopViewport(1);
682     QCOMPARE(compareOne.x, desktopOne.x);
683     QCOMPARE(compareOne.y, desktopOne.y);
684     const NETPoint compareTwo = rootInfo.desktopViewport(2);
685     QCOMPARE(compareTwo.x, desktopTwo.x);
686     QCOMPARE(compareTwo.y, desktopTwo.y);
687     const NETPoint compareThree = rootInfo.desktopViewport(3);
688     QCOMPARE(compareThree.x, 0);
689     QCOMPARE(compareThree.y, 0);
690 
691     // compare with the X property
692     QVERIFY(atom != XCB_ATOM_NONE);
693     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 8);
694     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
695     QVERIFY(!reply.isNull());
696     QCOMPARE(reply->format, uint8_t(32));
697     QCOMPARE(reply->value_len, uint32_t(8));
698     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
699     QCOMPARE(data[0], uint32_t(desktopOne.x));
700     QCOMPARE(data[1], uint32_t(desktopOne.y));
701     QCOMPARE(data[2], uint32_t(desktopTwo.x));
702     QCOMPARE(data[3], uint32_t(desktopTwo.y));
703     QCOMPARE(data[4], uint32_t(0));
704     QCOMPARE(data[5], uint32_t(0));
705     QCOMPARE(data[6], uint32_t(0));
706     QCOMPARE(data[7], uint32_t(0));
707 
708     // wait for our property - two events
709     waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport);
710     waitForPropertyChange(&rootInfo, atom, NET::DesktopViewport);
711     const NETPoint compareOne2 = rootInfo.desktopViewport(1);
712     QCOMPARE(compareOne2.x, desktopOne.x);
713     QCOMPARE(compareOne2.y, desktopOne.y);
714     const NETPoint compareTwo2 = rootInfo.desktopViewport(2);
715     QCOMPARE(compareTwo2.x, desktopTwo.x);
716     QCOMPARE(compareTwo2.y, desktopTwo.y);
717     const NETPoint compareThree2 = rootInfo.desktopViewport(3);
718     QCOMPARE(compareThree2.x, 0);
719     QCOMPARE(compareThree2.y, 0);
720 }
721 
testShowingDesktop_data()722 void NetRootInfoTestWM::testShowingDesktop_data()
723 {
724     QTest::addColumn<bool>("set");
725     QTest::addColumn<uint32_t>("setValue");
726 
727     QTest::newRow("true") << true << uint32_t(1);
728     QTest::newRow("false") << false << uint32_t(0);
729 }
730 
testShowingDesktop()731 void NetRootInfoTestWM::testShowingDesktop()
732 {
733     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_SHOWING_DESKTOP"));
734     QVERIFY(connection());
735     NETRootInfo
736         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
737     QFETCH(bool, set);
738     rootInfo.setShowingDesktop(set);
739     QCOMPARE(rootInfo.showingDesktop(), set);
740 
741     // compare with the X property
742     QVERIFY(atom != XCB_ATOM_NONE);
743     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 1);
744     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
745     QVERIFY(!reply.isNull());
746     QCOMPARE(reply->format, uint8_t(32));
747     QCOMPARE(reply->value_len, uint32_t(1));
748     QTEST(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()))[0], "setValue");
749 
750     // wait for our property
751     waitForPropertyChange(&rootInfo, atom, NET::Property(0), NET::WM2ShowingDesktop);
752     QCOMPARE(rootInfo.showingDesktop(), set);
753 }
754 
testWorkArea()755 void NetRootInfoTestWM::testWorkArea()
756 {
757     KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_WORKAREA"));
758     QVERIFY(connection());
759     NETRootInfo
760         rootInfo(connection(), m_supportWindow, s_wmName, NET::WMAllProperties, NET::AllTypesMask, NET::States(~0u), NET::WM2AllProperties, NET::Actions(~0u));
761     // we need to know the number of desktops, therefore setting it
762     rootInfo.setNumberOfDesktops(4);
763     NETRect desktopOne;
764     desktopOne.pos.x = 10;
765     desktopOne.pos.y = 5;
766     desktopOne.size.width = 1000;
767     desktopOne.size.height = 800;
768     NETRect desktopTwo;
769     desktopTwo.pos.x = 20;
770     desktopTwo.pos.y = 10;
771     desktopTwo.size.width = 800;
772     desktopTwo.size.height = 750;
773     rootInfo.setWorkArea(1, desktopOne);
774     rootInfo.setWorkArea(2, desktopTwo);
775 
776     const NETRect compareZero = rootInfo.workArea(0);
777     QCOMPARE(compareZero.pos.x, 0);
778     QCOMPARE(compareZero.pos.y, 0);
779     QCOMPARE(compareZero.size.width, 0);
780     QCOMPARE(compareZero.size.height, 0);
781     const NETRect compareOne = rootInfo.workArea(1);
782     QCOMPARE(compareOne.pos.x, desktopOne.pos.x);
783     QCOMPARE(compareOne.pos.y, desktopOne.pos.y);
784     QCOMPARE(compareOne.size.width, desktopOne.size.width);
785     QCOMPARE(compareOne.size.height, desktopOne.size.height);
786     const NETRect compareTwo = rootInfo.workArea(2);
787     QCOMPARE(compareTwo.pos.x, desktopTwo.pos.x);
788     QCOMPARE(compareTwo.pos.y, desktopTwo.pos.y);
789     QCOMPARE(compareTwo.size.width, desktopTwo.size.width);
790     QCOMPARE(compareTwo.size.height, desktopTwo.size.height);
791     const NETRect compareThree = rootInfo.workArea(3);
792     QCOMPARE(compareThree.pos.x, 0);
793     QCOMPARE(compareThree.pos.y, 0);
794     QCOMPARE(compareThree.size.width, 0);
795     QCOMPARE(compareThree.size.height, 0);
796 
797     // compare with the X property
798     QVERIFY(atom != XCB_ATOM_NONE);
799     xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, rootInfo.rootWindow(), atom, XCB_ATOM_CARDINAL, 0, 16);
800     Property reply(xcb_get_property_reply(connection(), cookie, nullptr));
801     QVERIFY(!reply.isNull());
802     QCOMPARE(reply->format, uint8_t(32));
803     QCOMPARE(reply->value_len, uint32_t(16));
804     uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
805     QCOMPARE(data[0], uint32_t(desktopOne.pos.x));
806     QCOMPARE(data[1], uint32_t(desktopOne.pos.y));
807     QCOMPARE(data[2], uint32_t(desktopOne.size.width));
808     QCOMPARE(data[3], uint32_t(desktopOne.size.height));
809     QCOMPARE(data[4], uint32_t(desktopTwo.pos.x));
810     QCOMPARE(data[5], uint32_t(desktopTwo.pos.y));
811     QCOMPARE(data[6], uint32_t(desktopTwo.size.width));
812     QCOMPARE(data[7], uint32_t(desktopTwo.size.height));
813     QCOMPARE(data[8], uint32_t(0));
814     QCOMPARE(data[9], uint32_t(0));
815     QCOMPARE(data[10], uint32_t(0));
816     QCOMPARE(data[11], uint32_t(0));
817     QCOMPARE(data[12], uint32_t(0));
818     QCOMPARE(data[13], uint32_t(0));
819     QCOMPARE(data[14], uint32_t(0));
820     QCOMPARE(data[15], uint32_t(0));
821 
822     // wait for our property - two events
823     waitForPropertyChange(&rootInfo, atom, NET::WorkArea);
824     waitForPropertyChange(&rootInfo, atom, NET::WorkArea);
825     const NETRect compareOne2 = rootInfo.workArea(1);
826     QCOMPARE(compareOne2.pos.x, desktopOne.pos.x);
827     QCOMPARE(compareOne2.pos.y, desktopOne.pos.y);
828     QCOMPARE(compareOne2.size.width, desktopOne.size.width);
829     QCOMPARE(compareOne2.size.height, desktopOne.size.height);
830     const NETRect compareTwo2 = rootInfo.workArea(2);
831     QCOMPARE(compareTwo2.pos.x, desktopTwo.pos.x);
832     QCOMPARE(compareTwo2.pos.y, desktopTwo.pos.y);
833     QCOMPARE(compareTwo2.size.width, desktopTwo.size.width);
834     QCOMPARE(compareTwo2.size.height, desktopTwo.size.height);
835     const NETRect compareThree2 = rootInfo.workArea(3);
836     QCOMPARE(compareThree2.pos.x, 0);
837     QCOMPARE(compareThree2.pos.y, 0);
838     QCOMPARE(compareThree2.size.width, 0);
839     QCOMPARE(compareThree2.size.height, 0);
840 }
841 
testDontCrashMapViewports()842 void NetRootInfoTestWM::testDontCrashMapViewports()
843 {
844     QProcess p;
845     const QString processName = QFINDTESTDATA("dontcrashmapviewport");
846     QVERIFY(!processName.isEmpty());
847 
848     p.start(processName, QStringList());
849     QVERIFY(p.waitForFinished());
850     QCOMPARE(p.exitStatus(), QProcess::NormalExit);
851     QCOMPARE(p.exitCode(), 0);
852 }
853 
854 QTEST_GUILESS_MAIN(NetRootInfoTestWM)
855 
856 #include "netrootinfotestwm.moc"
857