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