1187 lines
47 KiB
C++
1187 lines
47 KiB
C++
/*
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
SPDX-FileCopyrightText: 2019 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
#include "kwin_wayland_test.h"
|
|
|
|
#include "core/output.h"
|
|
#include "input_event.h"
|
|
#include "input_event_spy.h"
|
|
#include "pointer_input.h"
|
|
#include "wayland_server.h"
|
|
#include "window.h"
|
|
#include "workspace.h"
|
|
#include "xdgactivationv1.h"
|
|
|
|
#include <KWayland/Client/seat.h>
|
|
#include <KWayland/Client/surface.h>
|
|
#include <linux/input.h>
|
|
|
|
#if KWIN_BUILD_X11
|
|
#include "x11window.h"
|
|
|
|
#include <netwm.h>
|
|
#include <xcb/xcb_icccm.h>
|
|
#endif
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
static const QString s_socketName = QStringLiteral("wayland_test_activation-0");
|
|
|
|
class ActivationTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void init();
|
|
void cleanup();
|
|
|
|
void testSwitchToWindowToLeft();
|
|
void testSwitchToWindowToRight();
|
|
void testSwitchToWindowAbove();
|
|
void testSwitchToWindowBelow();
|
|
void testSwitchToWindowMaximized();
|
|
void testSwitchToWindowFullScreen();
|
|
void testActiveFullscreen();
|
|
void testXdgActivation();
|
|
void testNoDemandAttentionWithoutActivationRequest();
|
|
void testXdgActivationBeforeInitialCommit();
|
|
void testXdgActivationBeforeMap();
|
|
void testGlobalShortcutActivation();
|
|
void testFocusMovesFromClosedDialogToParentWindow();
|
|
void testFullAreaLayerSurfaceUnderlay_data();
|
|
void testFullAreaLayerSurfaceUnderlay();
|
|
void testFullAreaLayerSurfaceOverlay_data();
|
|
void testFullAreaLayerSurfaceOverlay();
|
|
void testPartialAreaLayerSurfaceOverlay_data();
|
|
void testPartialAreaLayerSurfaceOverlay();
|
|
|
|
private:
|
|
void stackScreensHorizontally();
|
|
void stackScreensVertically();
|
|
};
|
|
|
|
void ActivationTest::initTestCase()
|
|
{
|
|
qRegisterMetaType<Window *>();
|
|
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
|
kwinApp()->start();
|
|
Test::setOutputConfig({
|
|
Rect(0, 0, 1280, 1024),
|
|
Rect(1280, 0, 1280, 1024),
|
|
});
|
|
const auto outputs = workspace()->outputs();
|
|
QCOMPARE(outputs.count(), 2);
|
|
QCOMPARE(outputs[0]->geometry(), Rect(0, 0, 1280, 1024));
|
|
QCOMPARE(outputs[1]->geometry(), Rect(1280, 0, 1280, 1024));
|
|
}
|
|
|
|
void ActivationTest::init()
|
|
{
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::Low);
|
|
|
|
QVERIFY(Test::setupWaylandConnection(Test::AdditionalWaylandInterface::XdgActivation | Test::AdditionalWaylandInterface::Seat | Test::AdditionalWaylandInterface::LayerShellV1));
|
|
|
|
workspace()->setActiveOutput(QPoint(640, 512));
|
|
input()->pointer()->warp(QPoint(640, 512));
|
|
}
|
|
|
|
void ActivationTest::cleanup()
|
|
{
|
|
Test::destroyWaylandConnection();
|
|
|
|
stackScreensHorizontally();
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowToLeft()
|
|
{
|
|
// This test verifies that "Switch to Window to the Left" shortcut works.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensHorizontally();
|
|
|
|
// Create several windows on the left screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
window1.m_window->move(QPoint(300, 200));
|
|
window2.m_window->move(QPoint(500, 200));
|
|
|
|
// Create several windows on the right screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(1380, 200));
|
|
window4.m_window->move(QPoint(1580, 200));
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowToRight()
|
|
{
|
|
// This test verifies that "Switch to Window to the Right" shortcut works.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensHorizontally();
|
|
|
|
// Create several windows on the left screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
window1.m_window->move(QPoint(300, 200));
|
|
window2.m_window->move(QPoint(500, 200));
|
|
|
|
// Create several windows on the right screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(1380, 200));
|
|
window4.m_window->move(QPoint(1580, 200));
|
|
|
|
// Switch to window to the right.
|
|
workspace()->switchWindow(Workspace::DirectionEast);
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
// Switch to window to the right.
|
|
workspace()->switchWindow(Workspace::DirectionEast);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window to the right.
|
|
workspace()->switchWindow(Workspace::DirectionEast);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window to the right.
|
|
workspace()->switchWindow(Workspace::DirectionEast);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowAbove()
|
|
{
|
|
// This test verifies that "Switch to Window Above" shortcut works.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensVertically();
|
|
|
|
// Create several windows on the top screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
window1.m_window->move(QPoint(200, 300));
|
|
window2.m_window->move(QPoint(200, 500));
|
|
|
|
// Create several windows on the bottom screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(200, 1224));
|
|
window4.m_window->move(QPoint(200, 1424));
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowBelow()
|
|
{
|
|
// This test verifies that "Switch to Window Bottom" shortcut works.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensVertically();
|
|
|
|
// Create several windows on the top screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
window1.m_window->move(QPoint(200, 300));
|
|
window2.m_window->move(QPoint(200, 500));
|
|
|
|
// Create several windows on the bottom screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(200, 1224));
|
|
window4.m_window->move(QPoint(200, 1424));
|
|
|
|
// Switch to window below.
|
|
workspace()->switchWindow(Workspace::DirectionSouth);
|
|
QVERIFY(window1.m_window->isActive());
|
|
|
|
// Switch to window below.
|
|
workspace()->switchWindow(Workspace::DirectionSouth);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window below.
|
|
workspace()->switchWindow(Workspace::DirectionSouth);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window below.
|
|
workspace()->switchWindow(Workspace::DirectionSouth);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowMaximized()
|
|
{
|
|
// This test verifies that we switch to the top-most maximized window, i.e.
|
|
// the one that user sees at the moment. See bug 411356.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensHorizontally();
|
|
|
|
// Create several maximized windows on the left screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
QVERIFY(window1.waitSurfaceConfigure());
|
|
workspace()->slotWindowMaximize();
|
|
QVERIFY(window1.handleConfigure(Qt::red));
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
QVERIFY(window2.waitSurfaceConfigure());
|
|
workspace()->slotWindowMaximize();
|
|
QVERIFY(window2.handleConfigure(Qt::red));
|
|
|
|
const QList<Window *> stackingOrder = workspace()->stackingOrder();
|
|
QVERIFY(stackingOrder.indexOf(window1.m_window) < stackingOrder.indexOf(window2.m_window));
|
|
QCOMPARE(window1.m_window->maximizeMode(), MaximizeFull);
|
|
QCOMPARE(window2.m_window->maximizeMode(), MaximizeFull);
|
|
|
|
// Create several windows on the right screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(1380, 200));
|
|
window4.m_window->move(QPoint(1580, 200));
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window to the left.
|
|
workspace()->switchWindow(Workspace::DirectionWest);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::testSwitchToWindowFullScreen()
|
|
{
|
|
// This test verifies that we switch to the top-most fullscreen window, i.e.
|
|
// the one that user sees at the moment. See bug 411356.
|
|
|
|
// Prepare the test environment.
|
|
stackScreensVertically();
|
|
|
|
// Create several maximized windows on the top screen.
|
|
Test::XdgToplevelWindow window1;
|
|
QVERIFY(window1.show());
|
|
QVERIFY(window1.m_window->isActive());
|
|
QVERIFY(window1.waitSurfaceConfigure());
|
|
workspace()->slotWindowFullScreen();
|
|
QVERIFY(window1.handleConfigure(Qt::red));
|
|
|
|
Test::XdgToplevelWindow window2;
|
|
QVERIFY(window2.show());
|
|
QVERIFY(window2.m_window->isActive());
|
|
QVERIFY(window2.waitSurfaceConfigure());
|
|
workspace()->slotWindowFullScreen();
|
|
QVERIFY(window2.handleConfigure());
|
|
|
|
const QList<Window *> stackingOrder = workspace()->stackingOrder();
|
|
QVERIFY(stackingOrder.indexOf(window1.m_window) < stackingOrder.indexOf(window2.m_window));
|
|
QVERIFY(window1.m_window->isFullScreen());
|
|
QVERIFY(window2.m_window->isFullScreen());
|
|
|
|
// Create several windows on the bottom screen.
|
|
Test::XdgToplevelWindow window3;
|
|
QVERIFY(window3.show());
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
Test::XdgToplevelWindow window4;
|
|
QVERIFY(window4.show());
|
|
QVERIFY(window4.m_window->isActive());
|
|
|
|
window3.m_window->move(QPoint(200, 1224));
|
|
window4.m_window->move(QPoint(200, 1424));
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window3.m_window->isActive());
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window2.m_window->isActive());
|
|
|
|
// Switch to window above.
|
|
workspace()->switchWindow(Workspace::DirectionNorth);
|
|
QVERIFY(window4.m_window->isActive());
|
|
}
|
|
|
|
void ActivationTest::stackScreensHorizontally()
|
|
{
|
|
// Process pending wl_output bind requests before destroying all outputs.
|
|
QTest::qWait(1);
|
|
|
|
const QList<Rect> screenGeometries{
|
|
Rect(0, 0, 1280, 1024),
|
|
Rect(1280, 0, 1280, 1024),
|
|
};
|
|
Test::setOutputConfig(screenGeometries);
|
|
}
|
|
|
|
void ActivationTest::stackScreensVertically()
|
|
{
|
|
// Process pending wl_output bind requests before destroying all outputs.
|
|
QTest::qWait(1);
|
|
|
|
const QList<Rect> screenGeometries{
|
|
Rect(0, 0, 1280, 1024),
|
|
Rect(0, 1024, 1280, 1024),
|
|
};
|
|
Test::setOutputConfig(screenGeometries);
|
|
}
|
|
|
|
#if KWIN_BUILD_X11
|
|
static X11Window *createX11Window(xcb_connection_t *connection, const Rect &geometry, std::function<void(xcb_window_t)> setup = {})
|
|
{
|
|
xcb_window_t windowId = xcb_generate_id(connection);
|
|
xcb_create_window(connection, XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
|
geometry.x(),
|
|
geometry.y(),
|
|
geometry.width(),
|
|
geometry.height(),
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
xcb_size_hints_t hints{};
|
|
xcb_icccm_size_hints_set_position(&hints, 1, geometry.x(), geometry.y());
|
|
xcb_icccm_size_hints_set_size(&hints, 1, geometry.width(), geometry.height());
|
|
xcb_icccm_set_wm_normal_hints(connection, windowId, &hints);
|
|
|
|
if (setup) {
|
|
setup(windowId);
|
|
}
|
|
|
|
xcb_map_window(connection, windowId);
|
|
xcb_flush(connection);
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
|
if (!windowCreatedSpy.wait()) {
|
|
return nullptr;
|
|
}
|
|
return windowCreatedSpy.last().first().value<X11Window *>();
|
|
}
|
|
#endif
|
|
|
|
void ActivationTest::testActiveFullscreen()
|
|
{
|
|
#if KWIN_BUILD_X11
|
|
// Tests that an active X11 fullscreen window gets removed from the active layer
|
|
// when activating a Wayland window, even if there's a pending activation request
|
|
// for the X11 window
|
|
Test::setOutputConfig({Rect(0, 0, 1280, 1024)});
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
X11Window *x11Window = createX11Window(c.get(), Rect(0, 0, 100, 200));
|
|
|
|
// make it fullscreen
|
|
x11Window->setFullScreen(true);
|
|
QVERIFY(x11Window->isFullScreen());
|
|
QCOMPARE(x11Window->layer(), Layer::ActiveLayer);
|
|
|
|
// now, activate it again
|
|
workspace()->activateWindow(x11Window);
|
|
|
|
// now, create and activate a Wayland window
|
|
Test::XdgToplevelWindow waylandWindow;
|
|
QVERIFY(waylandWindow.show());
|
|
|
|
// the Wayland window should become active
|
|
// and the X11 window should not be in the active layer anymore
|
|
QSignalSpy stackingOrder(workspace(), &Workspace::stackingOrderChanged);
|
|
workspace()->activateWindow(waylandWindow.m_window);
|
|
QCOMPARE(workspace()->activeWindow(), waylandWindow.m_window);
|
|
QCOMPARE(x11Window->layer(), Layer::NormalLayer);
|
|
#endif
|
|
}
|
|
|
|
static std::vector<std::unique_ptr<Test::XdgToplevelWindow>> setupWindows(uint32_t &time)
|
|
{
|
|
// re-create the same setup every time for reduced confusion
|
|
std::vector<std::unique_ptr<Test::XdgToplevelWindow>> ret;
|
|
for (int i = 0; i < 3; i++) {
|
|
auto window = std::make_unique<Test::XdgToplevelWindow>();
|
|
window->show();
|
|
window->m_window->move(QPoint(150 * i, 0));
|
|
workspace()->activateWindow(window->m_window);
|
|
|
|
Test::pointerMotion(window->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(1, time++);
|
|
Test::pointerButtonReleased(1, time++);
|
|
ret.push_back(std::move(window));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void ActivationTest::testXdgActivation()
|
|
{
|
|
Test::setOutputConfig({Rect(0, 0, 1280, 1024)});
|
|
|
|
uint32_t time = 0;
|
|
|
|
auto windows = setupWindows(time);
|
|
|
|
QSignalSpy activationSpy(workspace(), &Workspace::windowActivated);
|
|
|
|
// activating a window without a valid token should fail
|
|
Test::xdgActivation()->activate(QString(), *windows[1]->m_surface);
|
|
QVERIFY(!activationSpy.wait(10));
|
|
|
|
// using the surface and a correct serial should make it work
|
|
auto token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows.back()->m_surface);
|
|
token->set_serial(windows.back()->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
Test::xdgActivation()->activate(token->commitAndWait(), *windows[1]->m_surface);
|
|
QVERIFY(activationSpy.wait());
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
// it should even work without the surface, if the serial is correct
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_serial(windows.back()->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
Test::xdgActivation()->activate(token->commitAndWait(), *windows[1]->m_surface);
|
|
QVERIFY(activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
// activation should still work if the window is closed after creating the token
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
QString result = token->commitAndWait();
|
|
|
|
windows[2]->unmap();
|
|
QVERIFY(activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
Test::xdgActivation()->activate(result, *windows[0]->m_surface);
|
|
QVERIFY(activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[0]->m_window);
|
|
|
|
// ...unless the user interacted with another window in between
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
|
|
windows[2]->unmap();
|
|
QVERIFY(activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
Test::pointerMotion(windows[1]->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(1, time++);
|
|
Test::pointerButtonReleased(1, time++);
|
|
|
|
Test::xdgActivation()->activate(result, *windows[0]->m_surface);
|
|
QVERIFY(!activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
// ensure that windows are only activated on show with a valid activation token
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::Extreme);
|
|
|
|
// creating a new window and immediately activating it should work
|
|
windows.clear();
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// activation should fail if the user clicks on another window in between
|
|
// creating the activation token and using it
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
|
|
Test::pointerMotion(windows[1]->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(1, time++);
|
|
Test::pointerButtonReleased(1, time++);
|
|
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
// if activation of a new window is not granted, it should be stacked behind the active window
|
|
QCOMPARE_LT(window.m_window->stackingOrder(), workspace()->activeWindow()->stackingOrder());
|
|
}
|
|
|
|
// same for pointer input on the currently focused window
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
|
|
Test::pointerMotion(windows[2]->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(1, time++);
|
|
Test::pointerButtonReleased(1, time++);
|
|
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), windows[2]->m_window);
|
|
QCOMPARE_LT(window.m_window->stackingOrder(), workspace()->activeWindow()->stackingOrder());
|
|
}
|
|
|
|
// same for keyboard input on the currently focused window
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
|
|
Test::keyboardKeyPressed(KEY_A, time++);
|
|
Test::keyboardKeyReleased(KEY_A, time++);
|
|
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), windows[2]->m_window);
|
|
// if activation of a new window is not granted, it should be stacked behind the active window
|
|
QCOMPARE_LT(window.m_window->stackingOrder(), workspace()->activeWindow()->stackingOrder());
|
|
}
|
|
|
|
// but modifier keys must not interfere in activation
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
|
|
Test::keyboardKeyPressed(KEY_LEFTSHIFT, time++);
|
|
Test::keyboardKeyReleased(KEY_LEFTSHIFT, time++);
|
|
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// a child window of the active one should always be automatically activated,
|
|
// even without an activation token
|
|
windows = setupWindows(time);
|
|
{
|
|
Test::XdgToplevelWindow window{[&windows](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
toplevel->set_parent(windows[2]->m_toplevel->object());
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// but a child window of a non-active one should not be automatically activated
|
|
windows = setupWindows(time);
|
|
{
|
|
Test::XdgToplevelWindow window{[&windows](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
toplevel->set_parent(windows[1]->m_toplevel->object());
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE_NE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// unless it has a valid activation token
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
result = token->commitAndWait();
|
|
{
|
|
Test::XdgToplevelWindow window{[&windows, &result](KWayland::Client::Surface *surface, Test::XdgToplevel *toplevel) {
|
|
toplevel->set_parent(windows[1]->m_toplevel->object());
|
|
Test::xdgActivation()->activate(result, *surface);
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// focus stealing prevention level High is more lax and should activate windows
|
|
// even with an invalid token if the app id matches the last granted activation token
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
windows = setupWindows(time);
|
|
windows[1]->m_toplevel->set_app_id("test_app_id");
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
token->set_app_id("test_app_id");
|
|
result = token->commitAndWait();
|
|
Test::xdgActivation()->activate(QString(), *windows[1]->m_surface);
|
|
QVERIFY(activationSpy.wait());
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
// new windows should also be activated if the app id matches,
|
|
// even if they don't actually request activation
|
|
windows = setupWindows(time);
|
|
token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*windows[2]->m_surface);
|
|
token->set_serial(windows[2]->m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
token->set_app_id("test_app_id_2");
|
|
result = token->commitAndWait();
|
|
{
|
|
Test::XdgToplevelWindow window{[&result](Test::XdgToplevel *toplevel) {
|
|
toplevel->set_app_id("test_app_id_2");
|
|
}};
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// with focus stealing prevention level Low, every new window should unconditionally be activated,
|
|
// even if it doesn't request an activation token at all
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::Low);
|
|
windows = setupWindows(time);
|
|
{
|
|
Test::XdgToplevelWindow window;
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
// with focus stealing prevention disabled, every activation token is considered "valid"
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::None);
|
|
windows = setupWindows(time);
|
|
Test::xdgActivation()->activate(QString(), *windows[1]->m_surface);
|
|
QVERIFY(activationSpy.wait());
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
}
|
|
|
|
void ActivationTest::testNoDemandAttentionWithoutActivationRequest()
|
|
{
|
|
// This test verifies that the demand attention state will not be set if kwin doesn't want a
|
|
// window to be activated and the client hasn't made an explicit activation request.
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a window
|
|
auto gooseWindow = std::make_unique<Test::XdgToplevelWindow>([](Test::XdgToplevel *toplevel) {
|
|
toplevel->set_app_id("org.kde.goose");
|
|
});
|
|
gooseWindow->show();
|
|
QVERIFY(gooseWindow->m_window->isActive());
|
|
QVERIFY(!gooseWindow->m_window->isDemandingAttention());
|
|
|
|
// Show another window.
|
|
auto wolfWindow = std::make_unique<Test::XdgToplevelWindow>([](Test::XdgToplevel *toplevel) {
|
|
toplevel->set_app_id("org.kde.wolf");
|
|
});
|
|
wolfWindow->show();
|
|
QVERIFY(!wolfWindow->m_window->isActive());
|
|
QVERIFY(!wolfWindow->m_window->isDemandingAttention());
|
|
}
|
|
|
|
static QString generateActivationToken(const Test::XdgToplevelWindow &window, const QString &appId)
|
|
{
|
|
std::unique_ptr<Test::XdgActivationToken> token = Test::xdgActivation()->createToken();
|
|
token->set_surface(*window.m_surface);
|
|
token->set_serial(window.m_window->lastUsageSerial(), *Test::waylandSeat());
|
|
token->set_app_id(appId);
|
|
return token->commitAndWait();
|
|
}
|
|
|
|
void ActivationTest::testXdgActivationBeforeInitialCommit()
|
|
{
|
|
// This test verifies that activation requests that are made before the initial xdg-toplevel
|
|
// commit are handled as expected, i.e. that the window will be eventually marked as active and
|
|
// that the Workspace state is not touched yet.
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a window
|
|
auto gooseWindow = std::make_unique<Test::XdgToplevelWindow>([](Test::XdgToplevel *toplevel) {
|
|
toplevel->set_app_id("org.kde.goose");
|
|
});
|
|
gooseWindow->show();
|
|
QVERIFY(gooseWindow->m_window->isActive());
|
|
|
|
// Click in the middle of the window so there is a proper usage serial.
|
|
uint32_t time = 0;
|
|
Test::pointerMotion(gooseWindow->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(BTN_LEFT, time++);
|
|
Test::pointerButtonReleased(BTN_LEFT, time++);
|
|
|
|
const QString invalidActivationToken = QStringLiteral("duckity duck");
|
|
const QString activationToken = generateActivationToken(*gooseWindow, QStringLiteral("org.kde.wolf"));
|
|
|
|
{
|
|
std::unique_ptr<KWayland::Client::Surface> duckSurface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> duckShellSurface(Test::createXdgToplevelSurface(duckSurface.get(), Test::CreationSetup::CreateOnly));
|
|
QSignalSpy toplevelConfigureRequestedSpy(duckShellSurface.get(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(duckShellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
// Wait for the compositor side window to be created.
|
|
QSignalSpy windowCreatedSpy(waylandServer(), &WaylandServer::windowCreated);
|
|
QVERIFY(windowCreatedSpy.wait());
|
|
Window *duckWindow = windowCreatedSpy.last().at(0).value<Window *>();
|
|
|
|
// Attempt to activate the window, waylandSync() is needed to make sure that the compositor
|
|
// side has processed the activation request before we check the compositor state.
|
|
//
|
|
// The activation token should only be cached, no other activation related state should be set.
|
|
Test::xdgActivation()->activate(invalidActivationToken, *duckSurface);
|
|
QVERIFY(Test::waylandSync());
|
|
QCOMPARE(duckWindow->activationToken(), invalidActivationToken);
|
|
QVERIFY(!duckWindow->isActive());
|
|
QVERIFY(!duckWindow->isDemandingAttention());
|
|
QVERIFY(!workspace()->stackingOrder().contains(duckWindow));
|
|
|
|
// Commit the initial state.
|
|
duckShellSurface->set_app_id(QStringLiteral("org.kde.duck"));
|
|
duckSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
// Map the window.
|
|
QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
|
|
duckShellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
|
|
Test::renderAndWaitForShown(duckSurface.get(), QSize(800, 600), Qt::blue);
|
|
|
|
// The window should not be activate because of the invalid activation token.
|
|
QVERIFY(!duckWindow->isActive());
|
|
QVERIFY(duckWindow->isDemandingAttention());
|
|
}
|
|
|
|
{
|
|
std::unique_ptr<KWayland::Client::Surface> wolfSurface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> wolfShellSurface(Test::createXdgToplevelSurface(wolfSurface.get(), Test::CreationSetup::CreateOnly));
|
|
QSignalSpy toplevelConfigureRequestedSpy(wolfShellSurface.get(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(wolfShellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
// Wait for the compositor side window to be created.
|
|
QSignalSpy windowCreatedSpy(waylandServer(), &WaylandServer::windowCreated);
|
|
QVERIFY(windowCreatedSpy.wait());
|
|
Window *wolfWindow = windowCreatedSpy.last().at(0).value<Window *>();
|
|
|
|
// Attempt to activate the window, waylandSync() is needed to make sure that the compositor
|
|
// side has processed the activation request before we check the compositor state.
|
|
//
|
|
// The activation token should only be cached, no other activation related state should be set.
|
|
Test::xdgActivation()->activate(activationToken, *wolfSurface);
|
|
QVERIFY(Test::waylandSync());
|
|
QCOMPARE(wolfWindow->activationToken(), activationToken);
|
|
QVERIFY(!wolfWindow->isActive());
|
|
QVERIFY(!wolfWindow->isDemandingAttention());
|
|
QVERIFY(!workspace()->stackingOrder().contains(wolfWindow));
|
|
|
|
// Commit the initial state.
|
|
wolfShellSurface->set_app_id(QStringLiteral("org.kde.wolf"));
|
|
wolfSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
// Map the window.
|
|
QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
|
|
wolfShellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
|
|
Test::renderAndWaitForShown(wolfSurface.get(), QSize(800, 600), Qt::blue);
|
|
|
|
// The window should be activated as expected.
|
|
QVERIFY(wolfWindow->isActive());
|
|
QVERIFY(!wolfWindow->isDemandingAttention());
|
|
}
|
|
}
|
|
|
|
void ActivationTest::testXdgActivationBeforeMap()
|
|
{
|
|
// This test verifies that activation requests that are made before the xdg-toplevel surface
|
|
// is mapped are handled as expected, i.e. that the window will be eventually marked as active
|
|
// and that the Workspace state is not touched yet.
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a window
|
|
auto gooseWindow = std::make_unique<Test::XdgToplevelWindow>([](Test::XdgToplevel *toplevel) {
|
|
toplevel->set_app_id("org.kde.goose");
|
|
});
|
|
gooseWindow->show();
|
|
QVERIFY(gooseWindow->m_window->isActive());
|
|
|
|
// Click in the middle of the window so there is a proper usage serial.
|
|
uint32_t time = 0;
|
|
Test::pointerMotion(gooseWindow->m_window->frameGeometry().center(), time++);
|
|
Test::pointerButtonPressed(BTN_LEFT, time++);
|
|
Test::pointerButtonReleased(BTN_LEFT, time++);
|
|
|
|
const QString invalidActivationToken = QStringLiteral("duckity duck");
|
|
const QString activationToken = generateActivationToken(*gooseWindow, QStringLiteral("org.kde.wolf"));
|
|
|
|
{
|
|
std::unique_ptr<KWayland::Client::Surface> duckSurface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> duckShellSurface(Test::createXdgToplevelSurface(duckSurface.get(), Test::CreationSetup::CreateOnly));
|
|
QSignalSpy toplevelConfigureRequestedSpy(duckShellSurface.get(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(duckShellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
// Wait for the compositor side window to be created.
|
|
QSignalSpy windowCreatedSpy(waylandServer(), &WaylandServer::windowCreated);
|
|
QVERIFY(windowCreatedSpy.wait());
|
|
Window *duckWindow = windowCreatedSpy.last().at(0).value<Window *>();
|
|
|
|
// Commit the initial state.
|
|
duckShellSurface->set_app_id(QStringLiteral("org.kde.duck"));
|
|
duckSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
// Attempt to activate the window, waylandSync() is needed to make sure that the compositor
|
|
// side has processed the activation request before we check the compositor state.
|
|
//
|
|
// The activation token should only be cached, no other activation related state should be set.
|
|
Test::xdgActivation()->activate(invalidActivationToken, *duckSurface);
|
|
QVERIFY(Test::waylandSync());
|
|
QCOMPARE(duckWindow->activationToken(), invalidActivationToken);
|
|
QVERIFY(!duckWindow->isActive());
|
|
QVERIFY(!duckWindow->isDemandingAttention());
|
|
QVERIFY(!workspace()->stackingOrder().contains(duckWindow));
|
|
|
|
// Map the window.
|
|
QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
|
|
duckShellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
|
|
Test::renderAndWaitForShown(duckSurface.get(), QSize(800, 600), Qt::blue);
|
|
|
|
// The window should not be activate because of the invalid activation token.
|
|
QVERIFY(!duckWindow->isActive());
|
|
QVERIFY(duckWindow->isDemandingAttention());
|
|
}
|
|
|
|
{
|
|
std::unique_ptr<KWayland::Client::Surface> wolfSurface(Test::createSurface());
|
|
std::unique_ptr<Test::XdgToplevel> wolfShellSurface(Test::createXdgToplevelSurface(wolfSurface.get(), Test::CreationSetup::CreateOnly));
|
|
QSignalSpy toplevelConfigureRequestedSpy(wolfShellSurface.get(), &Test::XdgToplevel::configureRequested);
|
|
QSignalSpy surfaceConfigureRequestedSpy(wolfShellSurface->xdgSurface(), &Test::XdgSurface::configureRequested);
|
|
|
|
// Wait for the compositor side window to be created.
|
|
QSignalSpy windowCreatedSpy(waylandServer(), &WaylandServer::windowCreated);
|
|
QVERIFY(windowCreatedSpy.wait());
|
|
Window *wolfWindow = windowCreatedSpy.last().at(0).value<Window *>();
|
|
|
|
// Commit the initial state.
|
|
wolfShellSurface->set_app_id(QStringLiteral("org.kde.wolf"));
|
|
wolfSurface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
QVERIFY(surfaceConfigureRequestedSpy.wait());
|
|
|
|
// Attempt to activate the window, waylandSync() is needed to make sure that the compositor
|
|
// side has processed the activation request before we check the compositor state.
|
|
//
|
|
// The activation token should only be cached, no other activation related state should be set.
|
|
Test::xdgActivation()->activate(activationToken, *wolfSurface);
|
|
QVERIFY(Test::waylandSync());
|
|
QCOMPARE(wolfWindow->activationToken(), activationToken);
|
|
QVERIFY(!wolfWindow->isActive());
|
|
QVERIFY(!wolfWindow->isDemandingAttention());
|
|
QVERIFY(!workspace()->stackingOrder().contains(wolfWindow));
|
|
|
|
// Map the window.
|
|
QCOMPARE(toplevelConfigureRequestedSpy.last().at(0).value<QSize>(), QSize(0, 0));
|
|
wolfShellSurface->xdgSurface()->ack_configure(surfaceConfigureRequestedSpy.last().at(0).value<quint32>());
|
|
Test::renderAndWaitForShown(wolfSurface.get(), QSize(800, 600), Qt::blue);
|
|
|
|
// The window should be activated as expected.
|
|
QVERIFY(wolfWindow->isActive());
|
|
QVERIFY(!wolfWindow->isDemandingAttention());
|
|
}
|
|
}
|
|
|
|
class TokenSpy : public InputEventSpy
|
|
{
|
|
public:
|
|
void keyboardKey(KeyboardKeyEvent *event) override
|
|
{
|
|
if (event->state == KeyboardKeyState::Pressed && event->key == Qt::Key::Key_A) {
|
|
latestToken = waylandServer()->xdgActivationIntegration()->requestPrivilegedToken(
|
|
nullptr, input()->lastInteractionSerial(), waylandServer()->seat(), "test");
|
|
}
|
|
}
|
|
|
|
QString latestToken;
|
|
};
|
|
|
|
void ActivationTest::testGlobalShortcutActivation()
|
|
{
|
|
Test::setOutputConfig({Rect(0, 0, 1280, 1024)});
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::Extreme);
|
|
|
|
// This spy needs to be used because normal keyboard shortcuts are signaled asynchronously,
|
|
// while the shortcut for launching an application creates the token immediately.
|
|
// The spy emulates the latter behavior.
|
|
TokenSpy tokenSpy;
|
|
input()->installInputEventSpy(&tokenSpy);
|
|
QSignalSpy activationSpy(workspace(), &Workspace::windowActivated);
|
|
|
|
uint32_t time = 0;
|
|
|
|
// just triggering the shortcut normally should have working activation
|
|
auto windows = setupWindows(time);
|
|
|
|
Test::keyboardKeyPressed(KEY_LEFTSHIFT, time++);
|
|
Test::keyboardKeyPressed(KEY_A, time++);
|
|
Test::keyboardKeyReleased(KEY_A, time++);
|
|
Test::keyboardKeyReleased(KEY_LEFTSHIFT, time++);
|
|
|
|
Test::xdgActivation()->activate(tokenSpy.latestToken, *windows[1]->m_surface);
|
|
QVERIFY(activationSpy.wait());
|
|
QCOMPARE(workspace()->activeWindow(), windows[1]->m_window);
|
|
|
|
// if we press a non-shift key after triggering the shortcut,
|
|
// activation should fail
|
|
windows = setupWindows(time);
|
|
|
|
Test::keyboardKeyPressed(KEY_LEFTSHIFT, time++);
|
|
Test::keyboardKeyPressed(KEY_A, time++);
|
|
Test::keyboardKeyReleased(KEY_A, time++);
|
|
Test::keyboardKeyReleased(KEY_LEFTSHIFT, time++);
|
|
|
|
Test::keyboardKeyPressed(KEY_B, time++);
|
|
Test::keyboardKeyReleased(KEY_B, time++);
|
|
|
|
Test::xdgActivation()->activate(tokenSpy.latestToken, *windows[1]->m_surface);
|
|
QVERIFY(!activationSpy.wait(10));
|
|
QCOMPARE(workspace()->activeWindow(), windows[2]->m_window);
|
|
}
|
|
|
|
void ActivationTest::testFocusMovesFromClosedDialogToParentWindow()
|
|
{
|
|
// This test verifies that input focus moves from a closed dialog to the parent window as expected.
|
|
|
|
QSignalSpy windowActivatedSpy(workspace(), &Workspace::windowActivated);
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface{Test::createSurface()};
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
auto window = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(window);
|
|
QCOMPARE(windowActivatedSpy.count(), 1);
|
|
QCOMPARE(windowActivatedSpy.last().at(0).value<Window *>(), window);
|
|
QCOMPARE(workspace()->activeWindow(), window);
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> transientSurface{Test::createSurface()};
|
|
std::unique_ptr<Test::XdgToplevel> transientShellSurface(Test::createXdgToplevelSurface(transientSurface.get()));
|
|
transientShellSurface->set_parent(shellSurface->object());
|
|
auto transient = Test::renderAndWaitForShown(transientSurface.get(), QSize(100, 50), Qt::blue);
|
|
QVERIFY(transient);
|
|
QCOMPARE(windowActivatedSpy.count(), 2);
|
|
QCOMPARE(windowActivatedSpy.last().at(0).value<Window *>(), transient);
|
|
QCOMPARE(workspace()->activeWindow(), transient);
|
|
|
|
transientShellSurface.reset();
|
|
transientSurface.reset();
|
|
QVERIFY(windowActivatedSpy.wait());
|
|
QCOMPARE(windowActivatedSpy.count(), 3);
|
|
QCOMPARE(windowActivatedSpy.last().at(0).value<Window *>(), window);
|
|
}
|
|
|
|
void ActivationTest::testFullAreaLayerSurfaceUnderlay_data()
|
|
{
|
|
QTest::addColumn<Test::LayerShellV1::layer>("layer");
|
|
|
|
QTest::addRow("background") << Test::LayerShellV1::layer_background;
|
|
QTest::addRow("bottom") << Test::LayerShellV1::layer_bottom;
|
|
}
|
|
|
|
void ActivationTest::testFullAreaLayerSurfaceUnderlay()
|
|
{
|
|
// This test verifies that an underlay layer shell surface that covers the whole screen will not
|
|
// be activated without a valid activation token by accident.
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a regular window.
|
|
Test::XdgToplevelWindow window;
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
|
|
// Show a fullscreen layer shell surface.
|
|
QFETCH(Test::LayerShellV1::layer, layer);
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test")));
|
|
shellSurface->set_layer(layer);
|
|
shellSurface->set_anchor(Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_right | Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_left);
|
|
shellSurface->set_size(0, 0);
|
|
shellSurface->set_keyboard_interactivity(1);
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested);
|
|
QVERIFY(configureRequestedSpy.wait());
|
|
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
|
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
|
Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red);
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
|
|
void ActivationTest::testFullAreaLayerSurfaceOverlay_data()
|
|
{
|
|
QTest::addColumn<Test::LayerShellV1::layer>("layer");
|
|
|
|
QTest::addRow("top") << Test::LayerShellV1::layer_top;
|
|
QTest::addRow("overlay") << Test::LayerShellV1::layer_overlay;
|
|
}
|
|
|
|
void ActivationTest::testFullAreaLayerSurfaceOverlay()
|
|
{
|
|
// This test verifies that an overlay layer shell surface that covers the whole screen will be
|
|
// activated even without a valid activation token (because it will cover the active window).
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a regular window.
|
|
Test::XdgToplevelWindow window;
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
|
|
// Show a fullscreen layer shell surface.
|
|
QFETCH(Test::LayerShellV1::layer, layer);
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test")));
|
|
shellSurface->set_layer(layer);
|
|
shellSurface->set_anchor(Test::LayerSurfaceV1::anchor_top | Test::LayerSurfaceV1::anchor_right | Test::LayerSurfaceV1::anchor_bottom | Test::LayerSurfaceV1::anchor_left);
|
|
shellSurface->set_size(0, 0);
|
|
shellSurface->set_keyboard_interactivity(1);
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested);
|
|
QVERIFY(configureRequestedSpy.wait());
|
|
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
|
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
|
Window *overlayWindow = Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red);
|
|
QCOMPARE(workspace()->activeWindow(), overlayWindow);
|
|
}
|
|
|
|
void ActivationTest::testPartialAreaLayerSurfaceOverlay_data()
|
|
{
|
|
QTest::addColumn<Test::LayerShellV1::layer>("layer");
|
|
|
|
QTest::addRow("top") << Test::LayerShellV1::layer_top;
|
|
QTest::addRow("overlay") << Test::LayerShellV1::layer_overlay;
|
|
}
|
|
|
|
void ActivationTest::testPartialAreaLayerSurfaceOverlay()
|
|
{
|
|
// This test verifies that an overlay layer shell surface that partially covers the screen will
|
|
// not be activated without an activation token by accident.
|
|
|
|
options->setFocusStealingPreventionLevel(FocusStealingPreventionLevel::High);
|
|
|
|
// Show a regular window.
|
|
Test::XdgToplevelWindow window;
|
|
QVERIFY(window.show());
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
|
|
// Show a fullscreen layer shell surface.
|
|
QFETCH(Test::LayerShellV1::layer, layer);
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
std::unique_ptr<Test::LayerSurfaceV1> shellSurface(Test::createLayerSurfaceV1(surface.get(), QStringLiteral("test")));
|
|
shellSurface->set_layer(layer);
|
|
shellSurface->set_size(1000, 1000);
|
|
shellSurface->set_keyboard_interactivity(1);
|
|
surface->commit(KWayland::Client::Surface::CommitFlag::None);
|
|
|
|
QSignalSpy configureRequestedSpy(shellSurface.get(), &Test::LayerSurfaceV1::configureRequested);
|
|
QVERIFY(configureRequestedSpy.wait());
|
|
const QSize requestedSize = configureRequestedSpy.last().at(1).toSize();
|
|
shellSurface->ack_configure(configureRequestedSpy.last().at(0).toUInt());
|
|
Test::renderAndWaitForShown(surface.get(), requestedSize, Qt::red);
|
|
QCOMPARE(workspace()->activeWindow(), window.m_window);
|
|
}
|
|
}
|
|
|
|
WAYLANDTEST_MAIN(KWin::ActivationTest)
|
|
#include "activation_test.moc"
|