File indexing completed on 2025-03-23 13:48:08
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 #include "kwin_wayland_test.h" 0010 0011 #include "atoms.h" 0012 #include "core/output.h" 0013 #include "core/outputbackend.h" 0014 #include "cursor.h" 0015 #include "deleted.h" 0016 #include "rules.h" 0017 #include "wayland_server.h" 0018 #include "workspace.h" 0019 #include "x11window.h" 0020 0021 #include <netwm.h> 0022 #include <xcb/xcb_icccm.h> 0023 0024 namespace KWin 0025 { 0026 0027 static const QString s_socketName = QStringLiteral("wayland_test_kwin_window_rules-0"); 0028 0029 class WindowRuleTest : public QObject 0030 { 0031 Q_OBJECT 0032 private Q_SLOTS: 0033 void initTestCase(); 0034 void init(); 0035 void cleanup(); 0036 void testApplyInitialMaximizeVert_data(); 0037 void testApplyInitialMaximizeVert(); 0038 void testWindowClassChange(); 0039 }; 0040 0041 void WindowRuleTest::initTestCase() 0042 { 0043 qRegisterMetaType<KWin::Window *>(); 0044 qRegisterMetaType<KWin::Deleted *>(); 0045 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0046 QVERIFY(waylandServer()->init(s_socketName)); 0047 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024) << QRect(1280, 0, 1280, 1024))); 0048 0049 kwinApp()->start(); 0050 QVERIFY(applicationStartedSpy.wait()); 0051 const auto outputs = workspace()->outputs(); 0052 QCOMPARE(outputs.count(), 2); 0053 QCOMPARE(outputs[0]->geometry(), QRect(0, 0, 1280, 1024)); 0054 QCOMPARE(outputs[1]->geometry(), QRect(1280, 0, 1280, 1024)); 0055 setenv("QT_QPA_PLATFORM", "wayland", true); 0056 } 0057 0058 void WindowRuleTest::init() 0059 { 0060 workspace()->setActiveOutput(QPoint(640, 512)); 0061 Cursors::self()->mouse()->setPos(QPoint(640, 512)); 0062 QVERIFY(waylandServer()->windows().isEmpty()); 0063 } 0064 0065 void WindowRuleTest::cleanup() 0066 { 0067 // discards old rules 0068 workspace()->rulebook()->load(); 0069 } 0070 0071 struct XcbConnectionDeleter 0072 { 0073 void operator()(xcb_connection_t *pointer) 0074 { 0075 xcb_disconnect(pointer); 0076 } 0077 }; 0078 0079 void WindowRuleTest::testApplyInitialMaximizeVert_data() 0080 { 0081 QTest::addColumn<QByteArray>("role"); 0082 0083 QTest::newRow("lowercase") << QByteArrayLiteral("mainwindow"); 0084 QTest::newRow("CamelCase") << QByteArrayLiteral("MainWindow"); 0085 } 0086 0087 void WindowRuleTest::testApplyInitialMaximizeVert() 0088 { 0089 // this test creates the situation of BUG 367554: creates a window and initial apply maximize vertical 0090 // the window is matched by class and role 0091 // load the rule 0092 QFile ruleFile(QFINDTESTDATA("./data/rules/maximize-vert-apply-initial")); 0093 QVERIFY(ruleFile.open(QIODevice::ReadOnly | QIODevice::Text)); 0094 QMetaObject::invokeMethod(workspace()->rulebook(), "temporaryRulesMessage", Q_ARG(QString, QString::fromUtf8(ruleFile.readAll()))); 0095 0096 // create the test window 0097 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0098 QVERIFY(!xcb_connection_has_error(c.get())); 0099 0100 xcb_window_t windowId = xcb_generate_id(c.get()); 0101 const QRect windowGeometry = QRect(0, 0, 10, 20); 0102 const uint32_t values[] = { 0103 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; 0104 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0105 windowGeometry.x(), 0106 windowGeometry.y(), 0107 windowGeometry.width(), 0108 windowGeometry.height(), 0109 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); 0110 xcb_size_hints_t hints; 0111 memset(&hints, 0, sizeof(hints)); 0112 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0113 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0114 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0115 xcb_icccm_set_wm_class(c.get(), windowId, 9, "kpat\0kpat"); 0116 0117 QFETCH(QByteArray, role); 0118 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_window_role, XCB_ATOM_STRING, 8, role.length(), role.constData()); 0119 0120 NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); 0121 info.setWindowType(NET::Normal); 0122 xcb_map_window(c.get(), windowId); 0123 xcb_flush(c.get()); 0124 0125 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0126 QVERIFY(windowCreatedSpy.wait()); 0127 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0128 QVERIFY(window); 0129 QVERIFY(window->isDecorated()); 0130 QVERIFY(!window->hasStrut()); 0131 QVERIFY(!window->isHiddenInternal()); 0132 QVERIFY(!window->readyForPainting()); 0133 QMetaObject::invokeMethod(window, "setReadyForPainting"); 0134 QVERIFY(window->readyForPainting()); 0135 QVERIFY(Test::waitForWaylandSurface(window)); 0136 QCOMPARE(window->maximizeMode(), MaximizeVertical); 0137 0138 // destroy window again 0139 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0140 xcb_unmap_window(c.get(), windowId); 0141 xcb_destroy_window(c.get(), windowId); 0142 xcb_flush(c.get()); 0143 QVERIFY(windowClosedSpy.wait()); 0144 } 0145 0146 void WindowRuleTest::testWindowClassChange() 0147 { 0148 KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); 0149 config->group("General").writeEntry("count", 1); 0150 0151 auto group = config->group("1"); 0152 group.writeEntry("above", true); 0153 group.writeEntry("aboverule", 2); 0154 group.writeEntry("wmclass", "org.kde.foo"); 0155 group.writeEntry("wmclasscomplete", false); 0156 group.writeEntry("wmclassmatch", 1); 0157 group.sync(); 0158 0159 workspace()->rulebook()->setConfig(config); 0160 workspace()->slotReconfigure(); 0161 0162 // create the test window 0163 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0164 QVERIFY(!xcb_connection_has_error(c.get())); 0165 0166 xcb_window_t windowId = xcb_generate_id(c.get()); 0167 const QRect windowGeometry = QRect(0, 0, 10, 20); 0168 const uint32_t values[] = { 0169 XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW}; 0170 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0171 windowGeometry.x(), 0172 windowGeometry.y(), 0173 windowGeometry.width(), 0174 windowGeometry.height(), 0175 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); 0176 xcb_size_hints_t hints; 0177 memset(&hints, 0, sizeof(hints)); 0178 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0179 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0180 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0181 xcb_icccm_set_wm_class(c.get(), windowId, 23, "org.kde.bar\0org.kde.bar"); 0182 0183 NETWinInfo info(c.get(), windowId, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); 0184 info.setWindowType(NET::Normal); 0185 xcb_map_window(c.get(), windowId); 0186 xcb_flush(c.get()); 0187 0188 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0189 QVERIFY(windowCreatedSpy.wait()); 0190 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0191 QVERIFY(window); 0192 QVERIFY(window->isDecorated()); 0193 QVERIFY(!window->hasStrut()); 0194 QVERIFY(!window->isHiddenInternal()); 0195 QVERIFY(!window->readyForPainting()); 0196 QMetaObject::invokeMethod(window, "setReadyForPainting"); 0197 QVERIFY(window->readyForPainting()); 0198 QVERIFY(Test::waitForWaylandSurface(window)); 0199 QCOMPARE(window->keepAbove(), false); 0200 0201 // now change class 0202 QSignalSpy windowClassChangedSpy{window, &X11Window::windowClassChanged}; 0203 xcb_icccm_set_wm_class(c.get(), windowId, 23, "org.kde.foo\0org.kde.foo"); 0204 xcb_flush(c.get()); 0205 QVERIFY(windowClassChangedSpy.wait()); 0206 QCOMPARE(window->keepAbove(), true); 0207 0208 // destroy window 0209 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0210 xcb_unmap_window(c.get(), windowId); 0211 xcb_destroy_window(c.get(), windowId); 0212 xcb_flush(c.get()); 0213 QVERIFY(windowClosedSpy.wait()); 0214 } 0215 0216 } 0217 0218 WAYLANDTEST_MAIN(KWin::WindowRuleTest) 0219 #include "window_rules_test.moc"