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