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 Q_DECLARE_METATYPE(NET::State)
15 Q_DECLARE_METATYPE(NET::States)
16 Q_DECLARE_METATYPE(NET::Actions)
17
18 class Property : public QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter>
19 {
20 public:
Property(xcb_get_property_reply_t * p=nullptr)21 Property(xcb_get_property_reply_t *p = nullptr)
22 : QScopedPointer<xcb_get_property_reply_t, QScopedPointerPodDeleter>(p)
23 {
24 }
25 };
26
27 // clang-format off
28 #define INFO NETWinInfo info(m_connection, m_testWindow, m_rootWindow, NET::WMAllProperties, NET::WM2AllProperties, NET::WindowManager);
29
30 #define ATOM(name) \
31 KXUtils::Atom atom(connection(), QByteArrayLiteral(#name));
32
33 #define UTF8 KXUtils::Atom utf8String(connection(), QByteArrayLiteral("UTF8_STRING"));
34
35 #define GETPROP(type, length, formatSize) \
36 xcb_get_property_cookie_t cookie = xcb_get_property_unchecked(connection(), false, m_testWindow, \
37 atom, type, 0, length); \
38 Property reply(xcb_get_property_reply(connection(), cookie, nullptr)); \
39 QVERIFY(!reply.isNull()); \
40 QCOMPARE(reply->format, uint8_t(formatSize)); \
41 QCOMPARE(reply->value_len, uint32_t(length));
42
43 #define VERIFYDELETED(t) \
44 xcb_get_property_cookie_t cookieDeleted = xcb_get_property_unchecked(connection(), false, m_testWindow, \
45 atom, t, 0, 1); \
46 Property replyDeleted(xcb_get_property_reply(connection(), cookieDeleted, nullptr)); \
47 QVERIFY(!replyDeleted.isNull()); \
48 QVERIFY(replyDeleted->type == XCB_ATOM_NONE);
49
50 class NetWinInfoTestWM : public QObject
51 {
52 Q_OBJECT
53 private Q_SLOTS:
54 void initTestCase();
55 void cleanupTestCase();
56 void init();
57 void cleanup();
58
59 void testState_data();
60 void testState();
61 void testVisibleName();
62 void testVisibleIconName();
63 void testDesktop_data();
64 void testDesktop();
65 void testOpacity_data();
66 void testOpacity();
67 void testAllowedActions_data();
68 void testAllowedActions();
69 void testFrameExtents();
70 void testFrameExtentsKDE();
71 void testFrameOverlap();
72 void testFullscreenMonitors();
73
74 private:
75 bool hasAtomFlag(const xcb_atom_t *atoms, int atomsLenght, const QByteArray &actionName);
76 void testStrut(xcb_atom_t atom, NETStrut(NETWinInfo:: *getter)(void)const, void (NETWinInfo:: *setter)(NETStrut), NET::Property property, NET::Property2 property2 = NET::Property2(0));
77 void waitForPropertyChange(NETWinInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2 = NET::Property2(0));
connection()78 xcb_connection_t *connection()
79 {
80 return m_connection;
81 }
82 xcb_connection_t *m_connection;
83 QVector<xcb_connection_t*> m_connections;
84 QScopedPointer<QProcess> m_xvfb;
85 xcb_window_t m_rootWindow;
86 xcb_window_t m_testWindow;
87 QByteArray m_displayNumber;
88 };
89
initTestCase()90 void NetWinInfoTestWM::initTestCase()
91 {
92 }
93
cleanupTestCase()94 void NetWinInfoTestWM::cleanupTestCase()
95 {
96 // close connection
97 while (!m_connections.isEmpty()) {
98 xcb_disconnect(m_connections.takeFirst());
99 }
100 }
101
init()102 void NetWinInfoTestWM::init()
103 {
104 // first reset just to be sure
105 m_connection = nullptr;
106 m_rootWindow = XCB_WINDOW_NONE;
107 m_testWindow = XCB_WINDOW_NONE;
108 // start Xvfb
109 m_xvfb.reset(new QProcess);
110
111 // use pipe to pass fd to Xvfb to get back the display id
112 int pipeFds[2];
113 QVERIFY(pipe(pipeFds) == 0);
114 m_xvfb->start(QStringLiteral("Xvfb"), QStringList{ QStringLiteral("-displayfd"), QString::number(pipeFds[1]) });
115 QVERIFY(m_xvfb->waitForStarted());
116 QCOMPARE(m_xvfb->state(), QProcess::Running);
117
118 // reads from pipe, closes write side
119 close(pipeFds[1]);
120
121 QFile readPipe;
122 QVERIFY(readPipe.open(pipeFds[0], QIODevice::ReadOnly, QFileDevice::AutoCloseHandle));
123 QByteArray displayNumber = readPipe.readLine();
124 readPipe.close();
125
126 displayNumber.prepend(QByteArray(":"));
127 displayNumber.remove(displayNumber.size() -1, 1);
128 m_displayNumber = displayNumber;
129
130 // create X connection
131 int screen = 0;
132 m_connection = xcb_connect(displayNumber.constData(), &screen);
133 QVERIFY(m_connection);
134 QVERIFY(!xcb_connection_has_error(m_connection));
135 m_rootWindow = KXUtils::rootWindow(m_connection, screen);
136
137 // create test window
138 m_testWindow = xcb_generate_id(m_connection);
139 uint32_t values[] = {XCB_EVENT_MASK_PROPERTY_CHANGE};
140 xcb_create_window(m_connection, XCB_COPY_FROM_PARENT, m_testWindow,
141 m_rootWindow,
142 0, 0, 100, 100, 0, XCB_COPY_FROM_PARENT,
143 XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values);
144 // and map it
145 xcb_map_window(m_connection, m_testWindow);
146 }
147
cleanup()148 void NetWinInfoTestWM::cleanup()
149 {
150 // destroy test window
151 xcb_unmap_window(m_connection, m_testWindow);
152 xcb_destroy_window(m_connection, m_testWindow);
153 m_testWindow = XCB_WINDOW_NONE;
154
155 // delay till clenupTestCase as otherwise xcb reuses the same memory address
156 m_connections << connection();
157 // kill Xvfb
158 m_xvfb->terminate();
159 m_xvfb->waitForFinished();
160 }
161
waitForPropertyChange(NETWinInfo * info,xcb_atom_t atom,NET::Property prop,NET::Property2 prop2)162 void NetWinInfoTestWM::waitForPropertyChange(NETWinInfo *info, xcb_atom_t atom, NET::Property prop, NET::Property2 prop2)
163 {
164 while (true) {
165 KXUtils::ScopedCPointer<xcb_generic_event_t> event(xcb_wait_for_event(connection()));
166 if (event.isNull()) {
167 break;
168 }
169 if ((event->response_type & ~0x80) != XCB_PROPERTY_NOTIFY) {
170 continue;
171 }
172 xcb_property_notify_event_t *pe = reinterpret_cast<xcb_property_notify_event_t *>(event.data());
173 if (pe->window != m_testWindow) {
174 continue;
175 }
176 if (pe->atom != atom) {
177 continue;
178 }
179 NET::Properties dirty;
180 NET::Properties2 dirty2;
181 info->event(event.data(), &dirty, &dirty2);
182 if (prop != 0) {
183 QVERIFY(dirty & prop);
184 }
185 if (prop2 != 0) {
186 QVERIFY(dirty2 & prop2);
187 }
188 if (!prop) {
189 QCOMPARE(dirty, NET::Properties());
190 }
191 if (!prop2) {
192 QCOMPARE(dirty2, NET::Properties2());
193 }
194 break;
195 }
196 }
197
hasAtomFlag(const xcb_atom_t * atoms,int atomsLength,const QByteArray & actionName)198 bool NetWinInfoTestWM::hasAtomFlag(const xcb_atom_t *atoms, int atomsLength, const QByteArray &actionName)
199 {
200 KXUtils::Atom atom(connection(), actionName);
201 if (atom == XCB_ATOM_NONE) {
202 qDebug() << "get atom failed";
203 return false;
204 }
205 for (int i = 0; i < atomsLength; ++i) {
206 if (atoms[i] == atom) {
207 return true;
208 }
209 }
210 return false;
211 }
212
testAllowedActions_data()213 void NetWinInfoTestWM::testAllowedActions_data()
214 {
215 QTest::addColumn<NET::Actions>("actions");
216 QTest::addColumn<QVector<QByteArray> >("names");
217
218 const QByteArray move = QByteArrayLiteral("_NET_WM_ACTION_MOVE");
219 const QByteArray resize = QByteArrayLiteral("_NET_WM_ACTION_RESIZE");
220 const QByteArray minimize = QByteArrayLiteral("_NET_WM_ACTION_MINIMIZE");
221 const QByteArray shade = QByteArrayLiteral("_NET_WM_ACTION_SHADE");
222 const QByteArray stick = QByteArrayLiteral("_NET_WM_ACTION_STICK");
223 const QByteArray maxVert = QByteArrayLiteral("_NET_WM_ACTION_MAXIMIZE_VERT");
224 const QByteArray maxHoriz = QByteArrayLiteral("_NET_WM_ACTION_MAXIMIZE_HORZ");
225 const QByteArray fullscreen = QByteArrayLiteral("_NET_WM_ACTION_FULLSCREEN");
226 const QByteArray desktop = QByteArrayLiteral("_NET_WM_ACTION_CHANGE_DESKTOP");
227 const QByteArray close = QByteArrayLiteral("_NET_WM_ACTION_CLOSE");
228
229 QTest::newRow("move") << NET::Actions(NET::ActionMove) << (QVector<QByteArray>() << move);
230 QTest::newRow("resize") << NET::Actions(NET::ActionResize) << (QVector<QByteArray>() << resize);
231 QTest::newRow("minimize") << NET::Actions(NET::ActionMinimize) << (QVector<QByteArray>() << minimize);
232 QTest::newRow("shade") << NET::Actions(NET::ActionShade) << (QVector<QByteArray>() << shade);
233 QTest::newRow("stick") << NET::Actions(NET::ActionStick) << (QVector<QByteArray>() << stick);
234 QTest::newRow("maxVert") << NET::Actions(NET::ActionMaxVert) << (QVector<QByteArray>() << maxVert);
235 QTest::newRow("maxHoriz") << NET::Actions(NET::ActionMaxHoriz) << (QVector<QByteArray>() << maxHoriz);
236 QTest::newRow("fullscreen") << NET::Actions(NET::ActionFullScreen) << (QVector<QByteArray>() << fullscreen);
237 QTest::newRow("desktop") << NET::Actions(NET::ActionChangeDesktop) << (QVector<QByteArray>() << desktop);
238 QTest::newRow("close") << NET::Actions(NET::ActionClose) << (QVector<QByteArray>() << close);
239
240 QTest::newRow("none") << NET::Actions() << QVector<QByteArray>();
241
242 QTest::newRow("all") << NET::Actions(NET::ActionMove |
243 NET::ActionResize |
244 NET::ActionMinimize |
245 NET::ActionShade |
246 NET::ActionStick |
247 NET::ActionMaxVert |
248 NET::ActionMaxHoriz |
249 NET::ActionFullScreen |
250 NET::ActionChangeDesktop |
251 NET::ActionClose)
252 << (QVector<QByteArray>() << move << resize << minimize << shade <<
253 stick << maxVert << maxHoriz <<
254 fullscreen << desktop << close);
255 }
256
testAllowedActions()257 void NetWinInfoTestWM::testAllowedActions()
258 {
259 QVERIFY(connection());
260 ATOM(_NET_WM_ALLOWED_ACTIONS)
261 INFO
262
263 QCOMPARE(info.allowedActions(), NET::Actions());
264 QFETCH(NET::Actions, actions);
265 info.setAllowedActions(actions);
266 QCOMPARE(info.allowedActions(), actions);
267
268 // compare with the X property
269 QFETCH(QVector<QByteArray>, names);
270 QVERIFY(atom != XCB_ATOM_NONE);
271 GETPROP(XCB_ATOM_ATOM, names.size(), 32)
272 xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply.data()));
273 for (int i = 0; i < names.size(); ++i) {
274 QVERIFY(hasAtomFlag(atoms, names.size(), names.at(i)));
275 }
276
277 // and wait for our event
278 waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2AllowedActions);
279 QCOMPARE(info.allowedActions(), actions);
280 }
281
testDesktop_data()282 void NetWinInfoTestWM::testDesktop_data()
283 {
284 QTest::addColumn<int>("desktop");
285 QTest::addColumn<uint32_t>("propertyDesktop");
286
287 QTest::newRow("1") << 1 << uint32_t(0);
288 QTest::newRow("4") << 4 << uint32_t(3);
289 QTest::newRow("on all") << int(NET::OnAllDesktops) << uint32_t(~0);
290 }
291
testDesktop()292 void NetWinInfoTestWM::testDesktop()
293 {
294 QVERIFY(connection());
295 ATOM(_NET_WM_DESKTOP)
296 INFO
297
298 QCOMPARE(info.desktop(), 0);
299 QFETCH(int, desktop);
300 info.setDesktop(desktop);
301 QCOMPARE(info.desktop(), desktop);
302
303 // compare with the X property
304 QVERIFY(atom != XCB_ATOM_NONE);
305 GETPROP(XCB_ATOM_CARDINAL, 1, 32)
306 QTEST(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()))[0], "propertyDesktop");
307
308 // and wait for our event
309 waitForPropertyChange(&info, atom, NET::WMDesktop);
310 QCOMPARE(info.desktop(), desktop);
311
312 // delete it
313 info.setDesktop(0);
314 QCOMPARE(info.desktop(), 0);
315 VERIFYDELETED(XCB_ATOM_CARDINAL)
316
317 // and wait for our event
318 waitForPropertyChange(&info, atom, NET::WMDesktop);
319 QCOMPARE(info.desktop(), 0);
320 }
321
testStrut(xcb_atom_t atom,NETStrut (NETWinInfo::* getter)(void)const,void (NETWinInfo::* setter)(NETStrut),NET::Property property,NET::Property2 property2)322 void NetWinInfoTestWM::testStrut(xcb_atom_t atom, NETStrut(NETWinInfo:: *getter)(void)const, void (NETWinInfo:: *setter)(NETStrut), NET::Property property, NET::Property2 property2)
323 {
324 INFO
325
326 NETStrut extents = (info.*getter)();
327 QCOMPARE(extents.bottom, 0);
328 QCOMPARE(extents.left, 0);
329 QCOMPARE(extents.right, 0);
330 QCOMPARE(extents.top, 0);
331
332 NETStrut newExtents;
333 newExtents.bottom = 10;
334 newExtents.left = 20;
335 newExtents.right = 30;
336 newExtents.top = 40;
337 (info.*setter)(newExtents);
338 extents = (info.*getter)();
339 QCOMPARE(extents.bottom, newExtents.bottom);
340 QCOMPARE(extents.left, newExtents.left);
341 QCOMPARE(extents.right, newExtents.right);
342 QCOMPARE(extents.top, newExtents.top);
343
344 // compare with the X property
345 QVERIFY(atom != XCB_ATOM_NONE);
346 GETPROP(XCB_ATOM_CARDINAL, 4, 32)
347 uint32_t *data = reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()));
348 QCOMPARE(data[0], uint32_t(newExtents.left));
349 QCOMPARE(data[1], uint32_t(newExtents.right));
350 QCOMPARE(data[2], uint32_t(newExtents.top));
351 QCOMPARE(data[3], uint32_t(newExtents.bottom));
352
353 // and wait for our event
354 waitForPropertyChange(&info, atom, property, property2);
355 extents = (info.*getter)();
356 QCOMPARE(extents.bottom, newExtents.bottom);
357 QCOMPARE(extents.left, newExtents.left);
358 QCOMPARE(extents.right, newExtents.right);
359 QCOMPARE(extents.top, newExtents.top);
360 }
361
testFrameExtents()362 void NetWinInfoTestWM::testFrameExtents()
363 {
364 QVERIFY(connection());
365 ATOM(_NET_FRAME_EXTENTS)
366 testStrut(atom, &NETWinInfo::frameExtents, &NETWinInfo::setFrameExtents, NET::WMFrameExtents);
367 }
368
testFrameExtentsKDE()369 void NetWinInfoTestWM::testFrameExtentsKDE()
370 {
371 // same as testFrameExtents just with a different atom name
372 QVERIFY(connection());
373 ATOM(_KDE_NET_WM_FRAME_STRUT)
374 testStrut(atom, &NETWinInfo::frameExtents, &NETWinInfo::setFrameExtents, NET::WMFrameExtents);
375 }
376
testFrameOverlap()377 void NetWinInfoTestWM::testFrameOverlap()
378 {
379 QVERIFY(connection());
380 ATOM(_NET_WM_FRAME_OVERLAP)
381 testStrut(atom, &NETWinInfo::frameOverlap, &NETWinInfo::setFrameOverlap, NET::Property(0), NET::WM2FrameOverlap);
382 }
383
testOpacity_data()384 void NetWinInfoTestWM::testOpacity_data()
385 {
386 QTest::addColumn<uint32_t>("opacity");
387
388 QTest::newRow("0 %") << uint32_t(0);
389 QTest::newRow("50 %") << uint32_t(0x0000ffff);
390 QTest::newRow("100 %") << uint32_t(0xffffffff);
391 }
392
testOpacity()393 void NetWinInfoTestWM::testOpacity()
394 {
395 QVERIFY(connection());
396 ATOM(_NET_WM_WINDOW_OPACITY)
397 INFO
398
399 QCOMPARE(info.opacity(), static_cast<unsigned long>(0xffffffffU));
400 QFETCH(uint32_t, opacity);
401 info.setOpacity(opacity);
402 QCOMPARE(info.opacity(), static_cast<unsigned long>(opacity));
403
404 // compare with the X property
405 QVERIFY(atom != XCB_ATOM_NONE);
406 GETPROP(XCB_ATOM_CARDINAL, 1, 32)
407 QCOMPARE(reinterpret_cast<uint32_t *>(xcb_get_property_value(reply.data()))[0], opacity);
408
409 // and wait for our event
410 waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2Opacity);
411 QCOMPARE(info.opacity(), static_cast<unsigned long>(opacity));
412 }
413
testState_data()414 void NetWinInfoTestWM::testState_data()
415 {
416 QTest::addColumn<NET::States>("states");
417 QTest::addColumn<QVector<QByteArray> >("names");
418
419 const QByteArray modal = QByteArrayLiteral("_NET_WM_STATE_MODAL");
420 const QByteArray sticky = QByteArrayLiteral("_NET_WM_STATE_STICKY");
421 const QByteArray maxVert = QByteArrayLiteral("_NET_WM_STATE_MAXIMIZED_VERT");
422 const QByteArray maxHoriz = QByteArrayLiteral("_NET_WM_STATE_MAXIMIZED_HORZ");
423 const QByteArray shaded = QByteArrayLiteral("_NET_WM_STATE_SHADED");
424 const QByteArray skipTaskbar = QByteArrayLiteral("_NET_WM_STATE_SKIP_TASKBAR");
425 const QByteArray skipSwitcher = QByteArrayLiteral("_KDE_NET_WM_STATE_SKIP_SWITCHER");
426 const QByteArray keepAbove = QByteArrayLiteral("_NET_WM_STATE_ABOVE");
427 const QByteArray staysOnTop = QByteArrayLiteral("_NET_WM_STATE_STAYS_ON_TOP");
428 const QByteArray skipPager = QByteArrayLiteral("_NET_WM_STATE_SKIP_PAGER");
429 const QByteArray hidden = QByteArrayLiteral("_NET_WM_STATE_HIDDEN");
430 const QByteArray fullScreen = QByteArrayLiteral("_NET_WM_STATE_FULLSCREEN");
431 const QByteArray keepBelow = QByteArrayLiteral("_NET_WM_STATE_BELOW");
432 const QByteArray demandsAttention = QByteArrayLiteral("_NET_WM_STATE_DEMANDS_ATTENTION");
433 const QByteArray focused = QByteArrayLiteral("_NET_WM_STATE_FOCUSED");
434
435 QTest::newRow("modal") << NET::States(NET::Modal) << (QVector<QByteArray>() << modal);
436 QTest::newRow("sticky") << NET::States(NET::Sticky) << (QVector<QByteArray>() << sticky);
437 QTest::newRow("maxVert") << NET::States(NET::MaxVert) << (QVector<QByteArray>() << maxVert);
438 QTest::newRow("maxHoriz") << NET::States(NET::MaxHoriz) << (QVector<QByteArray>() << maxHoriz);
439 QTest::newRow("shaded") << NET::States(NET::Shaded) << (QVector<QByteArray>() << shaded);
440 QTest::newRow("skipTaskbar") << NET::States(NET::SkipTaskbar) << (QVector<QByteArray>() << skipTaskbar);
441 QTest::newRow("keepAbove") << NET::States(NET::KeepAbove) << (QVector<QByteArray>() << keepAbove << staysOnTop);
442 QTest::newRow("skipPager") << NET::States(NET::SkipPager) << (QVector<QByteArray>() << skipPager);
443 QTest::newRow("hidden") << NET::States(NET::Hidden) << (QVector<QByteArray>() << hidden);
444 QTest::newRow("fullScreen") << NET::States(NET::FullScreen) << (QVector<QByteArray>() << fullScreen);
445 QTest::newRow("keepBelow") << NET::States(NET::KeepBelow) << (QVector<QByteArray>() << keepBelow);
446 QTest::newRow("demandsAttention") << NET::States(NET::DemandsAttention) << (QVector<QByteArray>() << demandsAttention);
447 QTest::newRow("skipSwitcher") << NET::States(NET::SkipSwitcher) << (QVector<QByteArray>() << skipSwitcher);
448 QTest::newRow("focused") << NET::States(NET::Focused) << (QVector<QByteArray>() << focused);
449
450 // TODO: it's possible to be keep above and below at the same time?!?
451 QTest::newRow("all") << NET::States(NET::Modal |
452 NET::Sticky |
453 NET::Max |
454 NET::Shaded |
455 NET::SkipTaskbar |
456 NET::SkipPager |
457 NET::KeepAbove |
458 NET::KeepBelow |
459 NET::Hidden |
460 NET::FullScreen |
461 NET::DemandsAttention |
462 NET::SkipSwitcher |
463 NET::Focused)
464 << (QVector<QByteArray>() << modal << sticky << maxVert << maxHoriz
465 << shaded << skipTaskbar << keepAbove
466 << skipPager << hidden << fullScreen
467 << keepBelow << demandsAttention << staysOnTop << skipSwitcher << focused);
468 }
469
testState()470 void NetWinInfoTestWM::testState()
471 {
472 QVERIFY(connection());
473 ATOM(_NET_WM_STATE)
474 INFO
475
476 QCOMPARE(info.state(), NET::States());
477 QFETCH(NET::States, states);
478 info.setState(states, NET::States());
479 QCOMPARE(info.state(), states);
480
481 // compare with the X property
482 QFETCH(QVector<QByteArray>, names);
483 QVERIFY(atom != XCB_ATOM_NONE);
484 GETPROP(XCB_ATOM_ATOM, names.size(), 32)
485 xcb_atom_t *atoms = reinterpret_cast<xcb_atom_t *>(xcb_get_property_value(reply.data()));
486 for (int i = 0; i < names.size(); ++i) {
487 QVERIFY(hasAtomFlag(atoms, names.size(), names.at(i)));
488 }
489
490 // and wait for our event
491 waitForPropertyChange(&info, atom, NET::WMState);
492 QCOMPARE(info.state(), states);
493 }
494
testVisibleIconName()495 void NetWinInfoTestWM::testVisibleIconName()
496 {
497 QVERIFY(connection());
498 ATOM(_NET_WM_VISIBLE_ICON_NAME)
499 UTF8
500 INFO
501
502 QVERIFY(!info.visibleIconName());
503 info.setVisibleIconName("foo");
504 QCOMPARE(info.visibleIconName(), "foo");
505
506 // compare with the X property
507 QVERIFY(atom != XCB_ATOM_NONE);
508 QVERIFY(utf8String != XCB_ATOM_NONE);
509 GETPROP(utf8String, 3, 8)
510 QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.data())), "foo");
511
512 // and wait for our event
513 waitForPropertyChange(&info, atom, NET::WMVisibleIconName);
514 QCOMPARE(info.visibleIconName(), "foo");
515
516 // delete the string
517 info.setVisibleIconName("");
518 QCOMPARE(info.visibleIconName(), "");
519 VERIFYDELETED(utf8String)
520 // and wait for our event
521 waitForPropertyChange(&info, atom, NET::WMVisibleIconName);
522 QVERIFY(!info.visibleIconName());
523
524 // set again, to ensure we don't leak on tear down
525 info.setVisibleIconName("bar");
526 xcb_flush(connection());
527 waitForPropertyChange(&info, atom, NET::WMVisibleIconName);
528 QCOMPARE(info.visibleIconName(), "bar");
529 }
530
testVisibleName()531 void NetWinInfoTestWM::testVisibleName()
532 {
533 QVERIFY(connection());
534 ATOM(_NET_WM_VISIBLE_NAME)
535 UTF8
536 INFO
537
538 QVERIFY(!info.visibleName());
539 info.setVisibleName("foo");
540 QCOMPARE(info.visibleName(), "foo");
541
542 // compare with the X property
543 QVERIFY(atom != XCB_ATOM_NONE);
544 QVERIFY(utf8String != XCB_ATOM_NONE);
545 GETPROP(utf8String, 3, 8)
546 QCOMPARE(reinterpret_cast<const char *>(xcb_get_property_value(reply.data())), "foo");
547
548 // and wait for our event
549 waitForPropertyChange(&info, atom, NET::WMVisibleName);
550 QCOMPARE(info.visibleName(), "foo");
551
552 // delete the string
553 info.setVisibleName("");
554 QCOMPARE(info.visibleName(), "");
555 VERIFYDELETED(utf8String)
556
557 // and wait for our event
558 waitForPropertyChange(&info, atom, NET::WMVisibleName);
559 QVERIFY(!info.visibleName());
560
561 // set again, to ensure we don't leak on tear down
562 info.setVisibleName("bar");
563 xcb_flush(connection());
564 waitForPropertyChange(&info, atom, NET::WMVisibleName);
565 QCOMPARE(info.visibleName(), "bar");
566 }
567
568 class MockWinInfo : public NETWinInfo
569 {
570 public:
MockWinInfo(xcb_connection_t * connection,xcb_window_t window,xcb_window_t rootWindow)571 MockWinInfo(xcb_connection_t *connection, xcb_window_t window, xcb_window_t rootWindow)
572 : NETWinInfo(connection, window, rootWindow, NET::WMAllProperties, NET::WM2AllProperties, NET::WindowManager)
573 {
574 }
575
576 protected:
changeFullscreenMonitors(NETFullscreenMonitors topology)577 void changeFullscreenMonitors(NETFullscreenMonitors topology) override
578 {
579 setFullscreenMonitors(topology);
580 }
581 };
582
testFullscreenMonitors()583 void NetWinInfoTestWM::testFullscreenMonitors()
584 {
585 // test case for BUG 391960
586 QVERIFY(connection());
587 const uint32_t maskValues[] = {
588 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
589 XCB_EVENT_MASK_KEY_PRESS |
590 XCB_EVENT_MASK_PROPERTY_CHANGE |
591 XCB_EVENT_MASK_COLOR_MAP_CHANGE |
592 XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT |
593 XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY |
594 XCB_EVENT_MASK_FOCUS_CHANGE | // For NotifyDetailNone
595 XCB_EVENT_MASK_EXPOSURE
596 };
597 QScopedPointer<xcb_generic_error_t, QScopedPointerPodDeleter> redirectCheck(xcb_request_check(connection(),
598 xcb_change_window_attributes_checked(connection(),
599 m_rootWindow,
600 XCB_CW_EVENT_MASK,
601 maskValues)));
602 QVERIFY(redirectCheck.isNull());
603
604 KXUtils::Atom atom(connection(), QByteArrayLiteral("_NET_WM_FULLSCREEN_MONITORS"));
605
606 // create client connection
607 auto clientConnection = xcb_connect(m_displayNumber.constData(), nullptr);
608 QVERIFY(clientConnection);
609 QVERIFY(!xcb_connection_has_error(clientConnection));
610
611 NETWinInfo clientInfo(clientConnection, m_testWindow, m_rootWindow, NET::WMAllProperties, NET::WM2AllProperties);
612 NETFullscreenMonitors topology;
613 topology.top = 1;
614 topology.bottom = 2;
615 topology.left = 3;
616 topology.right = 4;
617 clientInfo.setFullscreenMonitors(topology);
618 xcb_flush(clientConnection);
619
620 MockWinInfo info(connection(), m_testWindow, m_rootWindow);
621
622 while (true) {
623 KXUtils::ScopedCPointer<xcb_generic_event_t> event(xcb_wait_for_event(connection()));
624 if (event.isNull()) {
625 break;
626 }
627 if ((event->response_type & ~0x80) != XCB_CLIENT_MESSAGE) {
628 continue;
629 }
630
631 NET::Properties dirtyProtocols;
632 NET::Properties2 dirtyProtocols2;
633 QCOMPARE(info.fullscreenMonitors().isSet(), false);
634 info.event(event.data(), &dirtyProtocols, &dirtyProtocols2);
635 QCOMPARE(info.fullscreenMonitors().isSet(), true);
636 break;
637 }
638 xcb_flush(connection());
639 // now the property should be updated
640 waitForPropertyChange(&info, atom, NET::Property(0), NET::WM2FullscreenMonitors);
641
642 QCOMPARE(info.fullscreenMonitors().top, 1);
643 QCOMPARE(info.fullscreenMonitors().bottom, 2);
644 QCOMPARE(info.fullscreenMonitors().left, 3);
645 QCOMPARE(info.fullscreenMonitors().right, 4);
646
647 xcb_disconnect(clientConnection);
648 }
649
650 QTEST_GUILESS_MAIN(NetWinInfoTestWM)
651
652 #include "netwininfotestwm.moc"
653