diff --git a/autotests/integration/window_rules_test.cpp b/autotests/integration/window_rules_test.cpp --- a/autotests/integration/window_rules_test.cpp +++ b/autotests/integration/window_rules_test.cpp @@ -47,6 +47,7 @@ void cleanup(); void testApplyInitialMaximizeVert_data(); void testApplyInitialMaximizeVert(); + void testWindowClassChange(); }; void WindowRuleTest::initTestCase() @@ -165,6 +166,85 @@ QVERIFY(windowClosedSpy.wait()); } +void WindowRuleTest::testWindowClassChange() +{ + KSharedConfig::Ptr config = KSharedConfig::openConfig(QString(), KConfig::SimpleConfig); + config->group("General").writeEntry("count", 1); + + auto group = config->group("1"); + group.writeEntry("above", true); + group.writeEntry("aboverule", 2); + group.writeEntry("wmclass", "org.kde.foo"); + group.writeEntry("wmclasscomplete", false); + group.writeEntry("wmclassmatch", 1); + group.sync(); + + RuleBook::self()->setConfig(config); + workspace()->slotReconfigure(); + + // create the test window + QScopedPointer c(xcb_connect(nullptr, nullptr)); + QVERIFY(!xcb_connection_has_error(c.data())); + + xcb_window_t w = xcb_generate_id(c.data()); + const QRect windowGeometry = QRect(0, 0, 10, 20); + const uint32_t values[] = { + XCB_EVENT_MASK_ENTER_WINDOW | + XCB_EVENT_MASK_LEAVE_WINDOW + }; + xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(), + windowGeometry.x(), + windowGeometry.y(), + windowGeometry.width(), + windowGeometry.height(), + 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, XCB_CW_EVENT_MASK, values); + xcb_size_hints_t hints; + memset(&hints, 0, sizeof(hints)); + xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); + xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); + xcb_icccm_set_wm_normal_hints(c.data(), w, &hints); + xcb_icccm_set_wm_class(c.data(), w, 23, "org.kde.bar\0org.kde.bar"); + + NETWinInfo info(c.data(), w, rootWindow(), NET::WMAllProperties, NET::WM2AllProperties); + info.setWindowType(NET::Normal); + xcb_map_window(c.data(), w); + xcb_flush(c.data()); + + QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded); + QVERIFY(windowCreatedSpy.isValid()); + QVERIFY(windowCreatedSpy.wait()); + Client *client = windowCreatedSpy.last().first().value(); + QVERIFY(client); + QVERIFY(client->isDecorated()); + QVERIFY(!client->hasStrut()); + QVERIFY(!client->isHiddenInternal()); + QVERIFY(!client->readyForPainting()); + QMetaObject::invokeMethod(client, "setReadyForPainting"); + QVERIFY(client->readyForPainting()); + QVERIFY(!client->surface()); + QSignalSpy surfaceChangedSpy(client, &Toplevel::surfaceChanged); + QVERIFY(surfaceChangedSpy.isValid()); + QVERIFY(surfaceChangedSpy.wait()); + QVERIFY(client->surface()); + QCOMPARE(client->keepAbove(), false); + + // now change class + QSignalSpy windowClassChangedSpy{client, &Client::windowClassChanged}; + QVERIFY(windowClassChangedSpy.isValid()); + xcb_icccm_set_wm_class(c.data(), w, 23, "org.kde.foo\0org.kde.foo"); + xcb_flush(c.data()); + QVERIFY(windowClassChangedSpy.wait()); + QCOMPARE(client->keepAbove(), true); + + // destroy window + QSignalSpy windowClosedSpy(client, &Client::windowClosed); + QVERIFY(windowClosedSpy.isValid()); + xcb_unmap_window(c.data(), w); + xcb_destroy_window(c.data(), w); + xcb_flush(c.data()); + QVERIFY(windowClosedSpy.wait()); +} + } WAYLANDTEST_MAIN(KWin::WindowRuleTest) diff --git a/manage.cpp b/manage.cpp --- a/manage.cpp +++ b/manage.cpp @@ -132,6 +132,8 @@ setupWindowRules(false); setCaption(cap_normal, true); + connect(this, &Client::windowClassChanged, this, &Client::evaluateWindowRules); + if (Xcb::Extensions::self()->isShapeAvailable()) xcb_shape_select_input(connection(), window(), true); detectShape(window());