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