File indexing completed on 2025-03-23 13:48:09
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 "composite.h" 0013 #include "core/outputbackend.h" 0014 #include "cursor.h" 0015 #include "deleted.h" 0016 #include "effectloader.h" 0017 #include "effects.h" 0018 #include "wayland_server.h" 0019 #include "workspace.h" 0020 #include "x11window.h" 0021 0022 #include <KWayland/Client/surface.h> 0023 0024 #include <netwm.h> 0025 #include <xcb/xcb_icccm.h> 0026 0027 using namespace KWin; 0028 static const QString s_socketName = QStringLiteral("wayland_test_x11_window-0"); 0029 0030 class X11WindowTest : public QObject 0031 { 0032 Q_OBJECT 0033 private Q_SLOTS: 0034 void initTestCase_data(); 0035 void initTestCase(); 0036 void init(); 0037 void cleanup(); 0038 0039 void testMinimumSize(); 0040 void testMaximumSize(); 0041 void testResizeIncrements(); 0042 void testResizeIncrementsNoBaseSize(); 0043 void testTrimCaption_data(); 0044 void testTrimCaption(); 0045 void testFullscreenLayerWithActiveWaylandWindow(); 0046 void testFocusInWithWaylandLastActiveWindow(); 0047 void testX11WindowId(); 0048 void testCaptionChanges(); 0049 void testCaptionWmName(); 0050 void testCaptionMultipleWindows(); 0051 void testFullscreenWindowGroups(); 0052 void testActivateFocusedWindow(); 0053 void testReentrantMoveResize(); 0054 }; 0055 0056 void X11WindowTest::initTestCase_data() 0057 { 0058 QTest::addColumn<qreal>("scale"); 0059 QTest::newRow("normal") << 1.0; 0060 QTest::newRow("scaled2x") << 2.0; 0061 } 0062 0063 void X11WindowTest::initTestCase() 0064 { 0065 qRegisterMetaType<KWin::Deleted *>(); 0066 qRegisterMetaType<KWin::Window *>(); 0067 QSignalSpy applicationStartedSpy(kwinApp(), &Application::started); 0068 QVERIFY(waylandServer()->init(s_socketName)); 0069 QMetaObject::invokeMethod(kwinApp()->outputBackend(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(QVector<QRect>, QVector<QRect>() << QRect(0, 0, 1280, 1024))); 0070 kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig)); 0071 0072 kwinApp()->start(); 0073 QVERIFY(applicationStartedSpy.wait()); 0074 QVERIFY(KWin::Compositor::self()); 0075 } 0076 0077 void X11WindowTest::init() 0078 { 0079 QVERIFY(Test::setupWaylandConnection()); 0080 } 0081 0082 void X11WindowTest::cleanup() 0083 { 0084 Test::destroyWaylandConnection(); 0085 } 0086 0087 struct XcbConnectionDeleter 0088 { 0089 void operator()(xcb_connection_t *pointer) 0090 { 0091 xcb_disconnect(pointer); 0092 } 0093 }; 0094 0095 void X11WindowTest::testMinimumSize() 0096 { 0097 // This test verifies that the minimum size constraint is correctly applied. 0098 0099 QFETCH_GLOBAL(qreal, scale); 0100 kwinApp()->setXwaylandScale(scale); 0101 0102 // Create an xcb window. 0103 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0104 QVERIFY(!xcb_connection_has_error(c.get())); 0105 const QRect windowGeometry(0, 0, 100, 200); 0106 xcb_window_t windowId = xcb_generate_id(c.get()); 0107 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0108 windowGeometry.x(), 0109 windowGeometry.y(), 0110 windowGeometry.width(), 0111 windowGeometry.height(), 0112 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0113 xcb_size_hints_t hints; 0114 memset(&hints, 0, sizeof(hints)); 0115 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0116 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0117 xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); 0118 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0119 xcb_map_window(c.get(), windowId); 0120 xcb_flush(c.get()); 0121 0122 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0123 QVERIFY(windowCreatedSpy.wait()); 0124 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0125 QVERIFY(window); 0126 QVERIFY(window->isDecorated()); 0127 0128 QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0129 QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); 0130 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0131 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0132 0133 // Begin resize. 0134 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0135 QVERIFY(!window->isInteractiveResize()); 0136 workspace()->slotWindowResize(); 0137 QCOMPARE(workspace()->moveResizeWindow(), window); 0138 QCOMPARE(clientStartMoveResizedSpy.count(), 1); 0139 QVERIFY(window->isInteractiveResize()); 0140 0141 const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); 0142 0143 window->keyPressEvent(Qt::Key_Left); 0144 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0145 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); 0146 QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); 0147 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0148 QCOMPARE(window->clientSize().width(), 100 / scale); 0149 0150 window->keyPressEvent(Qt::Key_Right); 0151 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0152 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos); 0153 QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); 0154 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0155 QCOMPARE(window->clientSize().width(), 100 / scale); 0156 0157 window->keyPressEvent(Qt::Key_Right); 0158 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0159 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 0160 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0161 QVERIFY(frameGeometryChangedSpy.wait()); 0162 // whilst X11 window size goes through scale, the increment is a logical value kwin side 0163 QCOMPARE(window->clientSize().width(), 100 / scale + 8); 0164 0165 window->keyPressEvent(Qt::Key_Up); 0166 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0167 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8)); 0168 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0169 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0170 QCOMPARE(window->clientSize().height(), 200 / scale); 0171 0172 window->keyPressEvent(Qt::Key_Down); 0173 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0174 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 0175 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0176 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0177 QCOMPARE(window->clientSize().height(), 200 / scale); 0178 0179 window->keyPressEvent(Qt::Key_Down); 0180 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0181 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); 0182 QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); 0183 QVERIFY(frameGeometryChangedSpy.wait()); 0184 QCOMPARE(window->clientSize().height(), 200 / scale + 8); 0185 0186 // Finish the resize operation. 0187 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); 0188 window->keyPressEvent(Qt::Key_Enter); 0189 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0190 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0191 QVERIFY(!window->isInteractiveResize()); 0192 0193 // Destroy the window. 0194 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0195 xcb_unmap_window(c.get(), windowId); 0196 xcb_destroy_window(c.get(), windowId); 0197 xcb_flush(c.get()); 0198 QVERIFY(windowClosedSpy.wait()); 0199 c.reset(); 0200 } 0201 0202 void X11WindowTest::testMaximumSize() 0203 { 0204 // This test verifies that the maximum size constraint is correctly applied. 0205 QFETCH_GLOBAL(qreal, scale); 0206 kwinApp()->setXwaylandScale(scale); 0207 0208 // Create an xcb window. 0209 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0210 QVERIFY(!xcb_connection_has_error(c.get())); 0211 const QRect windowGeometry(0, 0, 100, 200); 0212 xcb_window_t windowId = xcb_generate_id(c.get()); 0213 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0214 windowGeometry.x(), 0215 windowGeometry.y(), 0216 windowGeometry.width(), 0217 windowGeometry.height(), 0218 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0219 xcb_size_hints_t hints; 0220 memset(&hints, 0, sizeof(hints)); 0221 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0222 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0223 xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height()); 0224 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0225 xcb_map_window(c.get(), windowId); 0226 xcb_flush(c.get()); 0227 0228 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0229 QVERIFY(windowCreatedSpy.wait()); 0230 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0231 QVERIFY(window); 0232 QVERIFY(window->isDecorated()); 0233 0234 QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0235 QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); 0236 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0237 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0238 0239 // Begin resize. 0240 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0241 QVERIFY(!window->isInteractiveResize()); 0242 workspace()->slotWindowResize(); 0243 QCOMPARE(workspace()->moveResizeWindow(), window); 0244 QCOMPARE(clientStartMoveResizedSpy.count(), 1); 0245 QVERIFY(window->isInteractiveResize()); 0246 0247 const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); 0248 0249 window->keyPressEvent(Qt::Key_Right); 0250 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0251 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 0252 QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); 0253 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0254 QCOMPARE(window->clientSize().width(), 100 / scale); 0255 0256 window->keyPressEvent(Qt::Key_Left); 0257 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0258 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos); 0259 QVERIFY(!clientStepUserMovedResizedSpy.wait(10)); 0260 QCOMPARE(clientStepUserMovedResizedSpy.count(), 0); 0261 QCOMPARE(window->clientSize().width(), 100 / scale); 0262 0263 window->keyPressEvent(Qt::Key_Left); 0264 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0265 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); 0266 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0267 QVERIFY(frameGeometryChangedSpy.wait()); 0268 QCOMPARE(window->clientSize().width(), 100 / scale - 8); 0269 0270 window->keyPressEvent(Qt::Key_Down); 0271 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0272 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8)); 0273 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0274 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0275 QCOMPARE(window->clientSize().height(), 200 / scale); 0276 0277 window->keyPressEvent(Qt::Key_Up); 0278 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0279 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0)); 0280 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0281 QVERIFY(!frameGeometryChangedSpy.wait(10)); 0282 QCOMPARE(window->clientSize().height(), 200 / scale); 0283 0284 window->keyPressEvent(Qt::Key_Up); 0285 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0286 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8)); 0287 QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); 0288 QVERIFY(frameGeometryChangedSpy.wait()); 0289 QCOMPARE(window->clientSize().height(), 200 / scale - 8); 0290 0291 // Finish the resize operation. 0292 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); 0293 window->keyPressEvent(Qt::Key_Enter); 0294 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0295 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0296 QVERIFY(!window->isInteractiveResize()); 0297 0298 // Destroy the window. 0299 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0300 xcb_unmap_window(c.get(), windowId); 0301 xcb_destroy_window(c.get(), windowId); 0302 xcb_flush(c.get()); 0303 QVERIFY(windowClosedSpy.wait()); 0304 c.reset(); 0305 } 0306 0307 void X11WindowTest::testResizeIncrements() 0308 { 0309 // This test verifies that the resize increments constraint is correctly applied. 0310 QFETCH_GLOBAL(qreal, scale); 0311 kwinApp()->setXwaylandScale(scale); 0312 0313 // Create an xcb window. 0314 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0315 QVERIFY(!xcb_connection_has_error(c.get())); 0316 const QRect windowGeometry(0, 0, 100, 200); 0317 xcb_window_t windowId = xcb_generate_id(c.get()); 0318 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0319 windowGeometry.x(), 0320 windowGeometry.y(), 0321 windowGeometry.width(), 0322 windowGeometry.height(), 0323 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0324 xcb_size_hints_t hints; 0325 memset(&hints, 0, sizeof(hints)); 0326 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0327 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0328 xcb_icccm_size_hints_set_base_size(&hints, windowGeometry.width(), windowGeometry.height()); 0329 xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5); 0330 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0331 xcb_map_window(c.get(), windowId); 0332 xcb_flush(c.get()); 0333 0334 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0335 QVERIFY(windowCreatedSpy.wait()); 0336 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0337 QVERIFY(window); 0338 QVERIFY(window->isDecorated()); 0339 0340 QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0341 QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); 0342 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0343 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0344 0345 // Begin resize. 0346 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0347 QVERIFY(!window->isInteractiveResize()); 0348 workspace()->slotWindowResize(); 0349 QCOMPARE(workspace()->moveResizeWindow(), window); 0350 QCOMPARE(clientStartMoveResizedSpy.count(), 1); 0351 QVERIFY(window->isInteractiveResize()); 0352 0353 const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); 0354 0355 window->keyPressEvent(Qt::Key_Right); 0356 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0357 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 0358 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0359 QVERIFY(frameGeometryChangedSpy.wait()); 0360 0361 // 100 + 8 logical pixels, rounded to resize increments. This will differ on scale 0362 const qreal horizontalResizeInc = 3 / scale; 0363 const qreal verticalResizeInc = 5 / scale; 0364 const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc; 0365 const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc; 0366 0367 QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0)); 0368 0369 window->keyPressEvent(Qt::Key_Down); 0370 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0371 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); 0372 QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); 0373 QVERIFY(frameGeometryChangedSpy.wait()); 0374 QCOMPARE(window->clientSize(), QSize(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc)); 0375 0376 // Finish the resize operation. 0377 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); 0378 window->keyPressEvent(Qt::Key_Enter); 0379 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0380 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0381 QVERIFY(!window->isInteractiveResize()); 0382 0383 // Destroy the window. 0384 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0385 xcb_unmap_window(c.get(), windowId); 0386 xcb_destroy_window(c.get(), windowId); 0387 xcb_flush(c.get()); 0388 QVERIFY(windowClosedSpy.wait()); 0389 c.reset(); 0390 } 0391 0392 void X11WindowTest::testResizeIncrementsNoBaseSize() 0393 { 0394 QFETCH_GLOBAL(qreal, scale); 0395 kwinApp()->setXwaylandScale(scale); 0396 0397 // Create an xcb window. 0398 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0399 QVERIFY(!xcb_connection_has_error(c.get())); 0400 const QRect windowGeometry(0, 0, 100, 200); 0401 xcb_window_t windowId = xcb_generate_id(c.get()); 0402 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0403 windowGeometry.x(), 0404 windowGeometry.y(), 0405 windowGeometry.width(), 0406 windowGeometry.height(), 0407 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0408 xcb_size_hints_t hints; 0409 memset(&hints, 0, sizeof(hints)); 0410 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0411 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0412 xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height()); 0413 xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5); 0414 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0415 xcb_map_window(c.get(), windowId); 0416 xcb_flush(c.get()); 0417 0418 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0419 QVERIFY(windowCreatedSpy.wait()); 0420 X11Window *window = windowCreatedSpy.last().first().value<X11Window *>(); 0421 QVERIFY(window); 0422 QVERIFY(window->isDecorated()); 0423 0424 QSignalSpy clientStartMoveResizedSpy(window, &Window::clientStartUserMovedResized); 0425 QSignalSpy clientStepUserMovedResizedSpy(window, &Window::clientStepUserMovedResized); 0426 QSignalSpy clientFinishUserMovedResizedSpy(window, &Window::clientFinishUserMovedResized); 0427 QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged); 0428 0429 // Begin resize. 0430 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0431 QVERIFY(!window->isInteractiveResize()); 0432 workspace()->slotWindowResize(); 0433 QCOMPARE(workspace()->moveResizeWindow(), window); 0434 QCOMPARE(clientStartMoveResizedSpy.count(), 1); 0435 QVERIFY(window->isInteractiveResize()); 0436 0437 const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos(); 0438 0439 window->keyPressEvent(Qt::Key_Right); 0440 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0441 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0)); 0442 QCOMPARE(clientStepUserMovedResizedSpy.count(), 1); 0443 QVERIFY(frameGeometryChangedSpy.wait()); 0444 0445 // 100 + 8 pixels, rounded to resize increments. This will differ on scale 0446 const qreal horizontalResizeInc = 3 / scale; 0447 const qreal verticalResizeInc = 5 / scale; 0448 const qreal expectedHorizontalResizeInc = std::floor(8. / horizontalResizeInc) * horizontalResizeInc; 0449 const qreal expectedVerticalResizeInc = std::floor(8. / verticalResizeInc) * verticalResizeInc; 0450 0451 QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, 0)); 0452 0453 window->keyPressEvent(Qt::Key_Down); 0454 window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos()); 0455 QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8)); 0456 QCOMPARE(clientStepUserMovedResizedSpy.count(), 2); 0457 QVERIFY(frameGeometryChangedSpy.wait()); 0458 QCOMPARE(window->clientSize(), QSizeF(100, 200) / scale + QSizeF(expectedHorizontalResizeInc, expectedVerticalResizeInc)); 0459 0460 // Finish the resize operation. 0461 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0); 0462 window->keyPressEvent(Qt::Key_Enter); 0463 QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1); 0464 QCOMPARE(workspace()->moveResizeWindow(), nullptr); 0465 QVERIFY(!window->isInteractiveResize()); 0466 0467 // Destroy the window. 0468 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0469 xcb_unmap_window(c.get(), windowId); 0470 xcb_destroy_window(c.get(), windowId); 0471 xcb_flush(c.get()); 0472 QVERIFY(windowClosedSpy.wait()); 0473 c.reset(); 0474 } 0475 0476 void X11WindowTest::testTrimCaption_data() 0477 { 0478 QFETCH_GLOBAL(qreal, scale); 0479 kwinApp()->setXwaylandScale(scale); 0480 0481 QTest::addColumn<QByteArray>("originalTitle"); 0482 QTest::addColumn<QByteArray>("expectedTitle"); 0483 0484 QTest::newRow("simplified") 0485 << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox") 0486 << QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox"); 0487 0488 QTest::newRow("with emojis") 0489 << QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276") 0490 << QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:"); 0491 } 0492 0493 void X11WindowTest::testTrimCaption() 0494 { 0495 QFETCH_GLOBAL(qreal, scale); 0496 kwinApp()->setXwaylandScale(scale); 0497 0498 // this test verifies that caption is properly trimmed 0499 0500 // create an xcb window 0501 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0502 QVERIFY(!xcb_connection_has_error(c.get())); 0503 const QRect windowGeometry(0, 0, 100, 200); 0504 xcb_window_t windowId = xcb_generate_id(c.get()); 0505 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0506 windowGeometry.x(), 0507 windowGeometry.y(), 0508 windowGeometry.width(), 0509 windowGeometry.height(), 0510 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0511 xcb_size_hints_t hints; 0512 memset(&hints, 0, sizeof(hints)); 0513 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0514 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0515 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0516 NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2()); 0517 QFETCH(QByteArray, originalTitle); 0518 winInfo.setName(originalTitle); 0519 xcb_map_window(c.get(), windowId); 0520 xcb_flush(c.get()); 0521 0522 // we should get a window for it 0523 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0524 QVERIFY(windowCreatedSpy.wait()); 0525 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0526 QVERIFY(window); 0527 QCOMPARE(window->window(), windowId); 0528 QFETCH(QByteArray, expectedTitle); 0529 QCOMPARE(window->caption(), QString::fromUtf8(expectedTitle)); 0530 0531 // and destroy the window again 0532 xcb_unmap_window(c.get(), windowId); 0533 xcb_flush(c.get()); 0534 0535 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0536 QVERIFY(windowClosedSpy.wait()); 0537 xcb_destroy_window(c.get(), windowId); 0538 c.reset(); 0539 } 0540 0541 void X11WindowTest::testFullscreenLayerWithActiveWaylandWindow() 0542 { 0543 QFETCH_GLOBAL(qreal, scale); 0544 kwinApp()->setXwaylandScale(scale); 0545 0546 // this test verifies that an X11 fullscreen window does not stay in the active layer 0547 // when a Wayland window is active, see BUG: 375759 0548 QCOMPARE(workspace()->outputs().count(), 1); 0549 0550 // first create an X11 window 0551 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0552 QVERIFY(!xcb_connection_has_error(c.get())); 0553 const QRect windowGeometry(0, 0, 100, 200); 0554 xcb_window_t windowId = xcb_generate_id(c.get()); 0555 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0556 windowGeometry.x(), 0557 windowGeometry.y(), 0558 windowGeometry.width(), 0559 windowGeometry.height(), 0560 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0561 xcb_size_hints_t hints; 0562 memset(&hints, 0, sizeof(hints)); 0563 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0564 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0565 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0566 xcb_map_window(c.get(), windowId); 0567 xcb_flush(c.get()); 0568 0569 // we should get a window for it 0570 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0571 QVERIFY(windowCreatedSpy.wait()); 0572 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0573 QVERIFY(window); 0574 QCOMPARE(window->window(), windowId); 0575 QVERIFY(!window->isFullScreen()); 0576 QVERIFY(window->isActive()); 0577 QCOMPARE(window->layer(), NormalLayer); 0578 0579 workspace()->slotWindowFullScreen(); 0580 QVERIFY(window->isFullScreen()); 0581 QCOMPARE(window->layer(), ActiveLayer); 0582 QCOMPARE(workspace()->stackingOrder().last(), window); 0583 0584 // now let's open a Wayland window 0585 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0586 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0587 auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0588 QVERIFY(waylandWindow); 0589 QVERIFY(waylandWindow->isActive()); 0590 QCOMPARE(waylandWindow->layer(), NormalLayer); 0591 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0592 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0593 QCOMPARE(window->layer(), NormalLayer); 0594 0595 // now activate fullscreen again 0596 workspace()->activateWindow(window); 0597 QTRY_VERIFY(window->isActive()); 0598 QCOMPARE(window->layer(), ActiveLayer); 0599 QCOMPARE(workspace()->stackingOrder().last(), window); 0600 QCOMPARE(workspace()->stackingOrder().last(), window); 0601 0602 // activate wayland window again 0603 workspace()->activateWindow(waylandWindow); 0604 QTRY_VERIFY(waylandWindow->isActive()); 0605 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0606 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0607 0608 // back to x window 0609 workspace()->activateWindow(window); 0610 QTRY_VERIFY(window->isActive()); 0611 // remove fullscreen 0612 QVERIFY(window->isFullScreen()); 0613 workspace()->slotWindowFullScreen(); 0614 QVERIFY(!window->isFullScreen()); 0615 // and fullscreen again 0616 workspace()->slotWindowFullScreen(); 0617 QVERIFY(window->isFullScreen()); 0618 QCOMPARE(workspace()->stackingOrder().last(), window); 0619 QCOMPARE(workspace()->stackingOrder().last(), window); 0620 0621 // activate wayland window again 0622 workspace()->activateWindow(waylandWindow); 0623 QTRY_VERIFY(waylandWindow->isActive()); 0624 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0625 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0626 0627 // back to X11 window 0628 workspace()->activateWindow(window); 0629 QTRY_VERIFY(window->isActive()); 0630 // remove fullscreen 0631 QVERIFY(window->isFullScreen()); 0632 workspace()->slotWindowFullScreen(); 0633 QVERIFY(!window->isFullScreen()); 0634 // and fullscreen through X API 0635 NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 0636 info.setState(NET::FullScreen, NET::FullScreen); 0637 NETRootInfo rootInfo(c.get(), NET::Properties()); 0638 rootInfo.setActiveWindow(windowId, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE); 0639 xcb_flush(c.get()); 0640 QTRY_VERIFY(window->isFullScreen()); 0641 QCOMPARE(workspace()->stackingOrder().last(), window); 0642 QCOMPARE(workspace()->stackingOrder().last(), window); 0643 0644 // activate wayland window again 0645 workspace()->activateWindow(waylandWindow); 0646 QTRY_VERIFY(waylandWindow->isActive()); 0647 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0648 QCOMPARE(workspace()->stackingOrder().last(), waylandWindow); 0649 QCOMPARE(window->layer(), NormalLayer); 0650 0651 // close the window 0652 shellSurface.reset(); 0653 surface.reset(); 0654 QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); 0655 QTRY_VERIFY(window->isActive()); 0656 QCOMPARE(window->layer(), ActiveLayer); 0657 0658 // and destroy the window again 0659 xcb_unmap_window(c.get(), windowId); 0660 xcb_flush(c.get()); 0661 } 0662 0663 void X11WindowTest::testFocusInWithWaylandLastActiveWindow() 0664 { 0665 // this test verifies that Workspace::allowWindowActivation does not crash if last client was a Wayland client 0666 QFETCH_GLOBAL(qreal, scale); 0667 kwinApp()->setXwaylandScale(scale); 0668 0669 // create an X11 window 0670 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0671 QVERIFY(!xcb_connection_has_error(c.get())); 0672 const QRect windowGeometry(0, 0, 100, 200); 0673 xcb_window_t windowId = xcb_generate_id(c.get()); 0674 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0675 windowGeometry.x(), 0676 windowGeometry.y(), 0677 windowGeometry.width(), 0678 windowGeometry.height(), 0679 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0680 xcb_size_hints_t hints; 0681 memset(&hints, 0, sizeof(hints)); 0682 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0683 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0684 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0685 xcb_map_window(c.get(), windowId); 0686 xcb_flush(c.get()); 0687 0688 // we should get a window for it 0689 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0690 QVERIFY(windowCreatedSpy.wait()); 0691 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0692 QVERIFY(window); 0693 QCOMPARE(window->window(), windowId); 0694 QVERIFY(window->isActive()); 0695 0696 // create Wayland window 0697 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0698 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0699 auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0700 QVERIFY(waylandWindow); 0701 QVERIFY(waylandWindow->isActive()); 0702 // activate no window 0703 workspace()->setActiveWindow(nullptr); 0704 QVERIFY(!waylandWindow->isActive()); 0705 QVERIFY(!workspace()->activeWindow()); 0706 // and close Wayland window again 0707 shellSurface.reset(); 0708 surface.reset(); 0709 QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); 0710 0711 // and try to activate the x11 window through X11 api 0712 const auto cookie = xcb_set_input_focus_checked(c.get(), XCB_INPUT_FOCUS_NONE, windowId, XCB_CURRENT_TIME); 0713 auto error = xcb_request_check(c.get(), cookie); 0714 QVERIFY(!error); 0715 // this accesses m_lastActiveWindow on trying to activate 0716 QTRY_VERIFY(window->isActive()); 0717 0718 // and destroy the window again 0719 xcb_unmap_window(c.get(), windowId); 0720 xcb_flush(c.get()); 0721 } 0722 0723 void X11WindowTest::testX11WindowId() 0724 { 0725 QFETCH_GLOBAL(qreal, scale); 0726 kwinApp()->setXwaylandScale(scale); 0727 0728 // create an X11 window 0729 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0730 QVERIFY(!xcb_connection_has_error(c.get())); 0731 const QRect windowGeometry(0, 0, 100, 200); 0732 xcb_window_t windowId = xcb_generate_id(c.get()); 0733 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0734 windowGeometry.x(), 0735 windowGeometry.y(), 0736 windowGeometry.width(), 0737 windowGeometry.height(), 0738 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0739 xcb_size_hints_t hints; 0740 memset(&hints, 0, sizeof(hints)); 0741 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0742 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0743 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0744 xcb_map_window(c.get(), windowId); 0745 xcb_flush(c.get()); 0746 0747 // we should get a window for it 0748 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0749 QVERIFY(windowCreatedSpy.wait()); 0750 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0751 QVERIFY(window); 0752 QCOMPARE(window->window(), windowId); 0753 QVERIFY(window->isActive()); 0754 QCOMPARE(window->window(), windowId); 0755 QCOMPARE(window->internalId().isNull(), false); 0756 const auto uuid = window->internalId(); 0757 QUuid deletedUuid; 0758 QCOMPARE(deletedUuid.isNull(), true); 0759 0760 connect(window, &X11Window::windowClosed, this, [&deletedUuid](Window *, Deleted *d) { 0761 deletedUuid = d->internalId(); 0762 }); 0763 0764 NETRootInfo rootInfo(c.get(), NET::WMAllProperties); 0765 QCOMPARE(rootInfo.activeWindow(), window->window()); 0766 0767 // activate a wayland window 0768 std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface()); 0769 std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get())); 0770 auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue); 0771 QVERIFY(waylandWindow); 0772 QVERIFY(waylandWindow->isActive()); 0773 xcb_flush(kwinApp()->x11Connection()); 0774 0775 NETRootInfo rootInfo2(c.get(), NET::WMAllProperties); 0776 QCOMPARE(rootInfo2.activeWindow(), 0u); 0777 0778 // back to X11 window 0779 shellSurface.reset(); 0780 surface.reset(); 0781 QVERIFY(Test::waitForWindowDestroyed(waylandWindow)); 0782 0783 QTRY_VERIFY(window->isActive()); 0784 NETRootInfo rootInfo3(c.get(), NET::WMAllProperties); 0785 QCOMPARE(rootInfo3.activeWindow(), window->window()); 0786 0787 // and destroy the window again 0788 xcb_unmap_window(c.get(), windowId); 0789 xcb_flush(c.get()); 0790 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0791 QVERIFY(windowClosedSpy.wait()); 0792 0793 QCOMPARE(deletedUuid.isNull(), false); 0794 QCOMPARE(deletedUuid, uuid); 0795 } 0796 0797 void X11WindowTest::testCaptionChanges() 0798 { 0799 QFETCH_GLOBAL(qreal, scale); 0800 kwinApp()->setXwaylandScale(scale); 0801 0802 // verifies that caption is updated correctly when the X11 window updates it 0803 // BUG: 383444 0804 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0805 QVERIFY(!xcb_connection_has_error(c.get())); 0806 const QRect windowGeometry(0, 0, 100, 200); 0807 xcb_window_t windowId = xcb_generate_id(c.get()); 0808 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0809 windowGeometry.x(), 0810 windowGeometry.y(), 0811 windowGeometry.width(), 0812 windowGeometry.height(), 0813 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0814 xcb_size_hints_t hints; 0815 memset(&hints, 0, sizeof(hints)); 0816 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0817 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0818 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0819 NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 0820 info.setName("foo"); 0821 xcb_map_window(c.get(), windowId); 0822 xcb_flush(c.get()); 0823 0824 // we should get a window for it 0825 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0826 QVERIFY(windowCreatedSpy.wait()); 0827 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0828 QVERIFY(window); 0829 QCOMPARE(window->window(), windowId); 0830 QCOMPARE(window->caption(), QStringLiteral("foo")); 0831 0832 QSignalSpy captionChangedSpy(window, &X11Window::captionChanged); 0833 info.setName("bar"); 0834 xcb_flush(c.get()); 0835 QVERIFY(captionChangedSpy.wait()); 0836 QCOMPARE(window->caption(), QStringLiteral("bar")); 0837 0838 // and destroy the window again 0839 QSignalSpy windowClosedSpy(window, &X11Window::windowClosed); 0840 xcb_unmap_window(c.get(), windowId); 0841 xcb_flush(c.get()); 0842 QVERIFY(windowClosedSpy.wait()); 0843 xcb_destroy_window(c.get(), windowId); 0844 c.reset(); 0845 } 0846 0847 void X11WindowTest::testCaptionWmName() 0848 { 0849 QFETCH_GLOBAL(qreal, scale); 0850 kwinApp()->setXwaylandScale(scale); 0851 0852 // this test verifies that a caption set through WM_NAME is read correctly 0853 0854 // open glxgears as that one only uses WM_NAME 0855 QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded); 0856 0857 QProcess glxgears; 0858 glxgears.setProgram(QStringLiteral("glxgears")); 0859 glxgears.start(); 0860 QVERIFY(glxgears.waitForStarted()); 0861 0862 QVERIFY(windowAddedSpy.wait()); 0863 QCOMPARE(windowAddedSpy.count(), 1); 0864 QCOMPARE(workspace()->clientList().count(), 1); 0865 X11Window *glxgearsWindow = workspace()->clientList().first(); 0866 QCOMPARE(glxgearsWindow->caption(), QStringLiteral("glxgears")); 0867 0868 glxgears.terminate(); 0869 QVERIFY(glxgears.waitForFinished()); 0870 } 0871 0872 void X11WindowTest::testCaptionMultipleWindows() 0873 { 0874 QFETCH_GLOBAL(qreal, scale); 0875 kwinApp()->setXwaylandScale(scale); 0876 0877 // BUG 384760 0878 // create first window 0879 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0880 QVERIFY(!xcb_connection_has_error(c.get())); 0881 const QRect windowGeometry(0, 0, 100, 200); 0882 xcb_window_t windowId = xcb_generate_id(c.get()); 0883 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0884 windowGeometry.x(), 0885 windowGeometry.y(), 0886 windowGeometry.width(), 0887 windowGeometry.height(), 0888 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0889 xcb_size_hints_t hints; 0890 memset(&hints, 0, sizeof(hints)); 0891 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0892 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0893 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0894 NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 0895 info.setName("foo"); 0896 xcb_map_window(c.get(), windowId); 0897 xcb_flush(c.get()); 0898 0899 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0900 QVERIFY(windowCreatedSpy.wait()); 0901 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0902 QVERIFY(window); 0903 QCOMPARE(window->window(), windowId); 0904 QCOMPARE(window->caption(), QStringLiteral("foo")); 0905 0906 // create second window with same caption 0907 xcb_window_t w2 = xcb_generate_id(c.get()); 0908 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), 0909 windowGeometry.x(), 0910 windowGeometry.y(), 0911 windowGeometry.width(), 0912 windowGeometry.height(), 0913 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0914 xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints); 0915 NETWinInfo info2(c.get(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 0916 info2.setName("foo"); 0917 info2.setIconName("foo"); 0918 xcb_map_window(c.get(), w2); 0919 xcb_flush(c.get()); 0920 0921 windowCreatedSpy.clear(); 0922 QVERIFY(windowCreatedSpy.wait()); 0923 X11Window *window2 = windowCreatedSpy.first().first().value<X11Window *>(); 0924 QVERIFY(window2); 0925 QCOMPARE(window2->window(), w2); 0926 QCOMPARE(window2->caption(), QStringLiteral("foo <2>\u200E")); 0927 NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); 0928 QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E")); 0929 QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E")); 0930 0931 QSignalSpy captionChangedSpy(window2, &X11Window::captionChanged); 0932 0933 NETWinInfo info4(c.get(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2()); 0934 info4.setName("foobar"); 0935 info4.setIconName("foobar"); 0936 xcb_map_window(c.get(), w2); 0937 xcb_flush(c.get()); 0938 0939 QVERIFY(captionChangedSpy.wait()); 0940 QCOMPARE(window2->caption(), QStringLiteral("foobar")); 0941 NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2()); 0942 QCOMPARE(QByteArray(info5.visibleName()), QByteArray()); 0943 QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray()); 0944 } 0945 0946 void X11WindowTest::testFullscreenWindowGroups() 0947 { 0948 // this test creates an X11 window and puts it to full screen 0949 // then a second window is created which is in the same window group 0950 // BUG: 388310 0951 0952 QFETCH_GLOBAL(qreal, scale); 0953 kwinApp()->setXwaylandScale(scale); 0954 0955 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 0956 QVERIFY(!xcb_connection_has_error(c.get())); 0957 const QRect windowGeometry(0, 0, 100, 200); 0958 xcb_window_t windowId = xcb_generate_id(c.get()); 0959 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 0960 windowGeometry.x(), 0961 windowGeometry.y(), 0962 windowGeometry.width(), 0963 windowGeometry.height(), 0964 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0965 xcb_size_hints_t hints; 0966 memset(&hints, 0, sizeof(hints)); 0967 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 0968 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 0969 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 0970 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); 0971 xcb_map_window(c.get(), windowId); 0972 xcb_flush(c.get()); 0973 0974 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 0975 QVERIFY(windowCreatedSpy.wait()); 0976 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 0977 QVERIFY(window); 0978 QCOMPARE(window->window(), windowId); 0979 QCOMPARE(window->isActive(), true); 0980 0981 QCOMPARE(window->isFullScreen(), false); 0982 QCOMPARE(window->layer(), NormalLayer); 0983 workspace()->slotWindowFullScreen(); 0984 QCOMPARE(window->isFullScreen(), true); 0985 QCOMPARE(window->layer(), ActiveLayer); 0986 0987 // now let's create a second window 0988 windowCreatedSpy.clear(); 0989 xcb_window_t w2 = xcb_generate_id(c.get()); 0990 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(), 0991 windowGeometry.x(), 0992 windowGeometry.y(), 0993 windowGeometry.width(), 0994 windowGeometry.height(), 0995 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 0996 xcb_size_hints_t hints2; 0997 memset(&hints2, 0, sizeof(hints2)); 0998 xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y()); 0999 xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height()); 1000 xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2); 1001 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); 1002 xcb_map_window(c.get(), w2); 1003 xcb_flush(c.get()); 1004 1005 QVERIFY(windowCreatedSpy.wait()); 1006 X11Window *window2 = windowCreatedSpy.first().first().value<X11Window *>(); 1007 QVERIFY(window2); 1008 QVERIFY(window != window2); 1009 QCOMPARE(window2->window(), w2); 1010 QCOMPARE(window2->isActive(), true); 1011 QCOMPARE(window2->group(), window->group()); 1012 // first window should be moved back to normal layer 1013 QCOMPARE(window->isActive(), false); 1014 QCOMPARE(window->isFullScreen(), true); 1015 QCOMPARE(window->layer(), NormalLayer); 1016 1017 // activating the fullscreen window again, should move it to active layer 1018 workspace()->activateWindow(window); 1019 QTRY_COMPARE(window->layer(), ActiveLayer); 1020 } 1021 1022 void X11WindowTest::testActivateFocusedWindow() 1023 { 1024 // The window manager may call XSetInputFocus() on a window that already has focus, in which 1025 // case no FocusIn event will be generated and the window won't be marked as active. This test 1026 // verifies that we handle that subtle case properly. 1027 1028 QFETCH_GLOBAL(qreal, scale); 1029 kwinApp()->setXwaylandScale(scale); 1030 1031 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> connection(xcb_connect(nullptr, nullptr)); 1032 QVERIFY(!xcb_connection_has_error(connection.get())); 1033 1034 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 1035 1036 const QRect windowGeometry(0, 0, 100, 200); 1037 xcb_size_hints_t hints; 1038 memset(&hints, 0, sizeof(hints)); 1039 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 1040 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 1041 1042 // Create the first test window. 1043 const xcb_window_t windowId1 = xcb_generate_id(connection.get()); 1044 xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId1, rootWindow(), 1045 windowGeometry.x(), windowGeometry.y(), 1046 windowGeometry.width(), windowGeometry.height(), 1047 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 1048 xcb_icccm_set_wm_normal_hints(connection.get(), windowId1, &hints); 1049 xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId1, 1050 atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId1); 1051 xcb_map_window(connection.get(), windowId1); 1052 xcb_flush(connection.get()); 1053 QVERIFY(windowCreatedSpy.wait()); 1054 X11Window *window1 = windowCreatedSpy.first().first().value<X11Window *>(); 1055 QVERIFY(window1); 1056 QCOMPARE(window1->window(), windowId1); 1057 QCOMPARE(window1->isActive(), true); 1058 1059 // Create the second test window. 1060 const xcb_window_t windowId2 = xcb_generate_id(connection.get()); 1061 xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId2, rootWindow(), 1062 windowGeometry.x(), windowGeometry.y(), 1063 windowGeometry.width(), windowGeometry.height(), 1064 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 1065 xcb_icccm_set_wm_normal_hints(connection.get(), windowId2, &hints); 1066 xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId2, 1067 atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId2); 1068 xcb_map_window(connection.get(), windowId2); 1069 xcb_flush(connection.get()); 1070 QVERIFY(windowCreatedSpy.wait()); 1071 X11Window *window2 = windowCreatedSpy.last().first().value<X11Window *>(); 1072 QVERIFY(window2); 1073 QCOMPARE(window2->window(), windowId2); 1074 QCOMPARE(window2->isActive(), true); 1075 1076 // When the second test window is destroyed, the window manager will attempt to activate the 1077 // next window in the focus chain, which is the first window. 1078 xcb_set_input_focus(connection.get(), XCB_INPUT_FOCUS_POINTER_ROOT, windowId1, XCB_CURRENT_TIME); 1079 xcb_destroy_window(connection.get(), windowId2); 1080 xcb_flush(connection.get()); 1081 QVERIFY(Test::waitForWindowDestroyed(window2)); 1082 QVERIFY(window1->isActive()); 1083 1084 // Destroy the first test window. 1085 xcb_destroy_window(connection.get(), windowId1); 1086 xcb_flush(connection.get()); 1087 QVERIFY(Test::waitForWindowDestroyed(window1)); 1088 } 1089 1090 void X11WindowTest::testReentrantMoveResize() 1091 { 1092 // This test verifies that calling moveResize() from a slot connected directly 1093 // to the frameGeometryChanged() signal won't cause an infinite recursion. 1094 1095 QFETCH_GLOBAL(qreal, scale); 1096 kwinApp()->setXwaylandScale(scale); 1097 1098 // Create a test window. 1099 std::unique_ptr<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr)); 1100 QVERIFY(!xcb_connection_has_error(c.get())); 1101 const QRect windowGeometry(0, 0, 100, 200); 1102 xcb_window_t windowId = xcb_generate_id(c.get()); 1103 xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(), 1104 windowGeometry.x(), 1105 windowGeometry.y(), 1106 windowGeometry.width(), 1107 windowGeometry.height(), 1108 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr); 1109 xcb_size_hints_t hints; 1110 memset(&hints, 0, sizeof(hints)); 1111 xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y()); 1112 xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height()); 1113 xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints); 1114 xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId); 1115 xcb_map_window(c.get(), windowId); 1116 xcb_flush(c.get()); 1117 1118 QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded); 1119 QVERIFY(windowCreatedSpy.wait()); 1120 X11Window *window = windowCreatedSpy.first().first().value<X11Window *>(); 1121 QVERIFY(window); 1122 QCOMPARE(window->pos(), QPoint(0, 0)); 1123 1124 // Let's pretend that there is a script that really wants the window to be at (100, 100). 1125 connect(window, &Window::frameGeometryChanged, this, [window]() { 1126 window->moveResize(QRectF(QPointF(100, 100), window->size())); 1127 }); 1128 1129 // Trigger the lambda above. 1130 window->move(QPoint(40, 50)); 1131 1132 // Eventually, the window will end up at (100, 100). 1133 QCOMPARE(window->pos(), QPoint(100, 100)); 1134 1135 // Destroy the test window. 1136 xcb_destroy_window(c.get(), windowId); 1137 xcb_flush(c.get()); 1138 QVERIFY(Test::waitForWindowDestroyed(window)); 1139 } 1140 1141 WAYLANDTEST_MAIN(X11WindowTest) 1142 #include "x11_window_test.moc"