2020-08-02 22:22:19 +00:00
|
|
|
|
/*
|
|
|
|
|
KWin - the KDE window manager
|
|
|
|
|
This file is part of the KDE project.
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
|
SPDX-FileCopyrightText: 2016 Martin Gräßlin <mgraesslin@kde.org>
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2020-08-02 22:22:19 +00:00
|
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
|
*/
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include "kwin_wayland_test.h"
|
2022-03-23 10:13:38 +00:00
|
|
|
|
|
2018-01-06 09:45:44 +00:00
|
|
|
|
#include "atoms.h"
|
2023-08-31 12:23:42 +00:00
|
|
|
|
#include "compositor.h"
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include "cursor.h"
|
2024-03-18 11:43:07 +00:00
|
|
|
|
#include "pointer_input.h"
|
2024-03-15 20:45:07 +00:00
|
|
|
|
#include "virtualdesktops.h"
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include "wayland_server.h"
|
|
|
|
|
#include "workspace.h"
|
2022-04-22 17:54:31 +00:00
|
|
|
|
#include "x11window.h"
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2017-07-23 09:50:58 +00:00
|
|
|
|
#include <KWayland/Client/surface.h>
|
|
|
|
|
|
2024-03-16 11:22:42 +00:00
|
|
|
|
#include <linux/input-event-codes.h>
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include <netwm.h>
|
|
|
|
|
#include <xcb/xcb_icccm.h>
|
|
|
|
|
|
|
|
|
|
using namespace KWin;
|
2022-04-22 17:54:31 +00:00
|
|
|
|
static const QString s_socketName = QStringLiteral("wayland_test_x11_window-0");
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
class X11WindowTest : public QObject
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
2022-03-23 10:13:38 +00:00
|
|
|
|
Q_OBJECT
|
2016-10-31 14:50:14 +00:00
|
|
|
|
private Q_SLOTS:
|
2022-05-16 20:13:39 +00:00
|
|
|
|
void initTestCase_data();
|
2016-10-31 14:50:14 +00:00
|
|
|
|
void initTestCase();
|
|
|
|
|
void init();
|
|
|
|
|
void cleanup();
|
|
|
|
|
|
2024-03-10 21:29:12 +00:00
|
|
|
|
void testMaximizedFull();
|
|
|
|
|
void testInitiallyMaximizedFull();
|
|
|
|
|
void testRequestMaximizedFull();
|
|
|
|
|
void testMaximizedVertical();
|
|
|
|
|
void testInitiallyMaximizedVertical();
|
|
|
|
|
void testRequestMaximizedVertical();
|
|
|
|
|
void testMaximizedHorizontal();
|
|
|
|
|
void testInitiallyMaximizedHorizontal();
|
|
|
|
|
void testRequestMaximizedHorizontal();
|
2024-03-18 11:43:07 +00:00
|
|
|
|
void testInteractiveMoveUnmaximizeFull();
|
|
|
|
|
void testInteractiveMoveUnmaximizeInitiallyFull();
|
|
|
|
|
void testInteractiveMoveUnmaximizeHorizontal();
|
|
|
|
|
void testInteractiveMoveUnmaximizeInitiallyHorizontal();
|
|
|
|
|
void testInteractiveMoveUnmaximizeVertical();
|
|
|
|
|
void testInteractiveMoveUnmaximizeInitiallyVertical();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
void testFullScreen();
|
|
|
|
|
void testInitiallyFullScreen();
|
|
|
|
|
void testRequestFullScreen();
|
|
|
|
|
void testFullscreenLayerWithActiveWaylandWindow();
|
|
|
|
|
void testFullscreenWindowGroups();
|
|
|
|
|
void testKeepBelow();
|
|
|
|
|
void testInitiallyKeepBelow();
|
|
|
|
|
void testKeepAbove();
|
|
|
|
|
void testInitiallyKeepAbove();
|
|
|
|
|
void testMinimized();
|
|
|
|
|
void testInitiallyMinimized();
|
|
|
|
|
void testRequestMinimized();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
void testSkipSwitcher();
|
|
|
|
|
void testInitiallySkipSwitcher();
|
|
|
|
|
void testRequestSkipSwitcher();
|
|
|
|
|
void testSkipPager();
|
|
|
|
|
void testInitiallySkipPager();
|
|
|
|
|
void testRequestSkipPager();
|
|
|
|
|
void testSkipTaskbar();
|
|
|
|
|
void testInitiallySkipTaskbar();
|
|
|
|
|
void testRequestSkipTaskbar();
|
2024-03-15 19:23:31 +00:00
|
|
|
|
void testOpacity();
|
2024-03-15 20:45:07 +00:00
|
|
|
|
void testDesktop_data();
|
|
|
|
|
void testDesktop();
|
|
|
|
|
void testInitialDesktop_data();
|
|
|
|
|
void testInitialDesktop();
|
|
|
|
|
void testChangeDesktop();
|
|
|
|
|
void testOnAllDesktops();
|
|
|
|
|
void testInitialOnAllDesktops();
|
|
|
|
|
void testChangeOnAllDesktops();
|
2024-03-16 11:22:42 +00:00
|
|
|
|
void testNetWmKeyboardMove();
|
|
|
|
|
void testNetWmKeyboardMoveCancel();
|
|
|
|
|
void testNetWmKeyboardResize();
|
|
|
|
|
void testNetWmKeyboardResizeCancel();
|
|
|
|
|
void testNetWmButtonMove();
|
|
|
|
|
void testNetWmButtonMoveNotPressed();
|
|
|
|
|
void testNetWmButtonMoveCancel();
|
|
|
|
|
void testNetWmButtonSize_data();
|
|
|
|
|
void testNetWmButtonSize();
|
|
|
|
|
void testNetWmButtonSizeNotPressed();
|
|
|
|
|
void testNetWmButtonSizeCancel();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
void testMinimumSize();
|
|
|
|
|
void testMaximumSize();
|
Fix captions with non-BMP characters
KWin replaces any non-printable character with a space. This check does not
handle surrogate pairs correctly. Additionally, translators sometimes insert
non-printable soft-hyphens into titles, which also cause KWin to display a
space instead.
This code adds the missing surrogate handling, and (to fix both issues), also
removes non-printable characters instead of replacing them with a space.
Also moved the changed test after these changes, so that changes in non-
printable characters do not cause unneeded redraws.
Unit tests adapted by Vlad Zagorodniy.
Test Plan:
kwrite /tmp/Test😣.txt shows correct title. I also tested actual non-printable
characters, such as 0x1A, and these are correctly omitted.
BUG: 376813
FIXED-IN: 5.15.5
Reviewers: #kwin, zzag
Reviewed By: #kwin, zzag
Subscribers: zzag, grasslin, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19052
2019-04-26 08:52:57 +00:00
|
|
|
|
void testTrimCaption_data();
|
|
|
|
|
void testTrimCaption();
|
2017-07-23 14:18:09 +00:00
|
|
|
|
void testFocusInWithWaylandLastActiveWindow();
|
2017-08-13 14:52:14 +00:00
|
|
|
|
void testCaptionChanges();
|
2017-08-01 19:28:36 +00:00
|
|
|
|
void testCaptionWmName();
|
2020-06-16 08:03:25 +00:00
|
|
|
|
void testActivateFocusedWindow();
|
Rework async geometry updates
Window management features were written with synchronous geometry
updates in mind. Currently, this poses a big problem on Wayland because
geometry updates are done in asynchronous fashion there.
At the moment, geometry is updated in a so called pseudo-asynchronous
fashion, meaning that the frame geometry will be reset to the old value
once geometry updates are unblocked. The main drawback of this approach
is that it is too error prone, the data flow is hard to comprehend, etc.
It is worth noting that there is already a machinery to perform async
geometry which is used during interactive move/resize operations.
This change extends the move/resize geometry usage beyond interactive
move/resize to make asynchronous geometry updates less error prone and
easier to comprehend.
With the proposed solution, all geometry updates must be done on the
move/resize geometry first. After that, the new geometry is passed on to
the Client-specific implementation of moveResizeInternal().
To be more specific, the frameGeometry() returns the current frame
geometry, it is primarily useful only to the scene. If you want to move
or resize a window, you need to use moveResizeGeometry() because it
corresponds to the last requested frame geometry.
It is worth noting that the moveResizeGeometry() returns the desired
bounding geometry. The client may commit the xdg_toplevel surface with a
slightly smaller window geometry, for example to enforce a specific
aspect ratio. The client is not allowed to resize beyond the size as
indicated in moveResizeGeometry().
The data flow is very simple: moveResize() updates the move/resize
geometry and calls the client-specific implementation of the
moveResizeInternal() method. Based on whether a configure event is
needed, moveResizeInternal() will update the frameGeometry() either
immediately or after the client commits a new buffer.
Unfortunately, both the compositor and xdg-shell clients try to update
the window geometry. It means that it's possible to have conflicts
between the two. With this change, the compositor's move resize geometry
will be synced only if there are no pending configure events, meaning
that the user doesn't try to resize the window.
2021-04-30 18:26:09 +00:00
|
|
|
|
void testReentrantMoveResize();
|
2024-03-19 21:31:16 +00:00
|
|
|
|
void testTransient();
|
|
|
|
|
void testGroupTransient();
|
|
|
|
|
void testCloseTransient();
|
|
|
|
|
void testCloseInactiveTransient();
|
|
|
|
|
void testCloseGroupTransient();
|
|
|
|
|
void testCloseInactiveGroupTransient();
|
2024-03-18 21:28:07 +00:00
|
|
|
|
void testModal();
|
|
|
|
|
void testGroupModal();
|
|
|
|
|
void testCloseModal();
|
|
|
|
|
void testCloseInactiveModal();
|
|
|
|
|
void testCloseGroupModal();
|
|
|
|
|
void testCloseInactiveGroupModal();
|
2016-10-31 14:50:14 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-05-16 20:13:39 +00:00
|
|
|
|
void X11WindowTest::initTestCase_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<qreal>("scale");
|
|
|
|
|
QTest::newRow("normal") << 1.0;
|
|
|
|
|
QTest::newRow("scaled2x") << 2.0;
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::initTestCase()
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
2022-04-22 17:39:12 +00:00
|
|
|
|
qRegisterMetaType<KWin::Window *>();
|
2020-07-07 09:32:29 +00:00
|
|
|
|
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
2020-12-09 13:06:15 +00:00
|
|
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
2023-05-08 10:16:00 +00:00
|
|
|
|
Test::setOutputConfig({QRect(0, 0, 1280, 1024)});
|
2017-07-23 14:18:09 +00:00
|
|
|
|
kwinApp()->setConfig(KSharedConfig::openConfig(QString(), KConfig::SimpleConfig));
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
|
|
|
|
kwinApp()->start();
|
2020-07-07 09:32:29 +00:00
|
|
|
|
QVERIFY(applicationStartedSpy.wait());
|
2017-07-23 09:50:58 +00:00
|
|
|
|
QVERIFY(KWin::Compositor::self());
|
2016-10-31 14:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::init()
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
2024-03-15 20:45:07 +00:00
|
|
|
|
VirtualDesktopManager::self()->setCount(2);
|
|
|
|
|
|
2024-03-14 19:44:12 +00:00
|
|
|
|
QFETCH_GLOBAL(qreal, scale);
|
|
|
|
|
kwinApp()->setXwaylandScale(scale);
|
|
|
|
|
|
2017-07-23 09:50:58 +00:00
|
|
|
|
QVERIFY(Test::setupWaylandConnection());
|
2016-10-31 14:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::cleanup()
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
2017-07-23 09:50:58 +00:00
|
|
|
|
Test::destroyWaylandConnection();
|
2016-10-31 14:50:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-10 21:29:12 +00:00
|
|
|
|
static X11Window *createWindow(xcb_connection_t *connection, const QRect &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;
|
|
|
|
|
memset(&hints, 0, sizeof(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 *>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testMaximizedFull()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that toggling maximized mode works as expected and state changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
const QRectF workArea = workspace()->clientArea(MaximizeArea, window);
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
window->maximize(MaximizeFull);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), workArea);
|
|
|
|
|
QCOMPARE(window->geometryRestore(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY((winInfo.state() & NET::Max) == NET::Max);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the window.
|
|
|
|
|
window->maximize(MaximizeRestore);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 2);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::Max));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyMaximizedFull()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already in the maximized state.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Max, NET::Max);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestMaximizedFull()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can toggle the maximized state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Max, NET::Max);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
|
|
|
|
|
// Unset maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::Max);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testMaximizedVertical()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that toggling maximized vertically mode works as expected and state changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
const QRectF workArea = workspace()->clientArea(MaximizeArea, window);
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
window->maximize(MaximizeVertical);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(originalGeometry.x(), workArea.y(), originalGeometry.width(), workArea.height()));
|
|
|
|
|
QCOMPARE(window->geometryRestore(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY((winInfo.state() & NET::Max) == NET::MaxVert);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the window.
|
|
|
|
|
window->maximize(MaximizeRestore);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 2);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::Max));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyMaximizedVertical()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already in the maximized vertically state.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxVert, NET::MaxVert);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestMaximizedVertical()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can toggle the maximized vertically state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxVert, NET::MaxVert);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
|
|
|
|
|
// Unset maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::MaxVert);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testMaximizedHorizontal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that toggling maximized horizontally mode works as expected and state changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
const QRectF workArea = workspace()->clientArea(MaximizeArea, window);
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
window->maximize(MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(workArea.x(), originalGeometry.y(), workArea.width(), originalGeometry.height()));
|
|
|
|
|
QCOMPARE(window->geometryRestore(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY((winInfo.state() & NET::Max) == NET::MaxHoriz);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the window.
|
|
|
|
|
window->maximize(MaximizeRestore);
|
|
|
|
|
QCOMPARE(maximizedChangedSpy.count(), 2);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::Max));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyMaximizedHorizontal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already in the maximized horizontally state.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxHoriz, NET::MaxHoriz);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestMaximizedHorizontal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can toggle the maximized horizontally state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxHoriz, NET::MaxHoriz);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy maximizedChangedSpy(window, &Window::maximizedChanged);
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
|
|
|
|
|
// Unset maximized state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::MaxHoriz);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(maximizedChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-18 11:43:07 +00:00
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeFull()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a maximized x11 window is going to be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
window->maximize(MaximizeFull);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 100), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(originalGeometry.width() * xOffset, originalGeometry.height() * yOffset), originalGeometry.size()));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 10), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(0, 10));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeInitiallyFull()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that an initially maximized x11 window will be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Max, NET::Max);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeFull);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeFull);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
const QSizeF restoredSize = window->geometryRestore().size();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 100), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(restoredSize.width() * xOffset, restoredSize.height() * yOffset), restoredSize));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 10), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(0, 10));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeHorizontal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a maximized horizontally x11 window is going to be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
window->maximize(MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(100, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(originalGeometry.width() * xOffset, originalGeometry.height() * yOffset), originalGeometry.size()));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(10, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(10, 0));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeInitiallyHorizontal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that an initially maximized horizontally x11 window will be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxHoriz, NET::MaxHoriz);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeHorizontal);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeHorizontal);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
const QSizeF restoredSize = window->geometryRestore().size();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(100, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(restoredSize.width() * xOffset, restoredSize.height() * yOffset), restoredSize));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(10, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(10, 0));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeVertical()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a maximized vertically x11 window is going to be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
window->maximize(MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 100), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(originalGeometry.width() * xOffset, originalGeometry.height() * yOffset), originalGeometry.size()));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 10), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(0, 10));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInteractiveMoveUnmaximizeInitiallyVertical()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that an initially maximized vertically x11 window will be properly unmaximized when it's dragged.
|
|
|
|
|
|
|
|
|
|
// Create the window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::MaxVert, NET::MaxVert);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Start interactive move.
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
|
|
|
|
const qreal xOffset = 0.2;
|
|
|
|
|
const qreal yOffset = 0.5;
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerMotion(QPointF(window->x() + window->width() * xOffset, window->y() + window->height() * yOffset), timestamp++);
|
|
|
|
|
window->performMouseCommand(Options::MouseMove, input()->pointer()->pos());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeVertical);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeVertical);
|
|
|
|
|
|
|
|
|
|
// Move the window to unmaximize it.
|
|
|
|
|
const QSizeF restoredSize = window->geometryRestore().size();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 100), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), QRectF(input()->pointer()->pos() - QPointF(restoredSize.width() * xOffset, restoredSize.height() * yOffset), restoredSize));
|
|
|
|
|
|
|
|
|
|
// Move the window again.
|
|
|
|
|
const QRectF normalGeometry = window->frameGeometry();
|
|
|
|
|
Test::pointerMotionRelative(QPointF(0, 10), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->maximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->requestedMaximizeMode(), MaximizeRestore);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), normalGeometry.translated(0, 10));
|
|
|
|
|
|
|
|
|
|
// Finish interactive move.
|
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-10 21:29:12 +00:00
|
|
|
|
void X11WindowTest::testFullScreen()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the fullscreen mode can be toggled and state changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Make the window maximized.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
const QRectF screenArea = workspace()->clientArea(ScreenArea, window);
|
|
|
|
|
QSignalSpy fullScreenChangedSpy(window, &Window::fullScreenChanged);
|
|
|
|
|
window->setFullScreen(true);
|
|
|
|
|
QCOMPARE(fullScreenChangedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->isRequestedFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), screenArea);
|
|
|
|
|
QCOMPARE(window->fullscreenGeometryRestore(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::FullScreen);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore the window.
|
|
|
|
|
window->setFullScreen(false);
|
|
|
|
|
QCOMPARE(fullScreenChangedSpy.count(), 2);
|
|
|
|
|
QCOMPARE(window->isFullScreen(), false);
|
|
|
|
|
QCOMPARE(window->isRequestedFullScreen(), false);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::FullScreen));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyFullScreen()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already in the fullscreen state.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::FullScreen, NET::FullScreen);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->isRequestedFullScreen(), true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestFullScreen()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can toggle the fullscreen state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set fullscreen state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::FullScreen, NET::FullScreen);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy fullScreenChangedSpy(window, &Window::fullScreenChanged);
|
|
|
|
|
QVERIFY(fullScreenChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->isRequestedFullScreen(), true);
|
|
|
|
|
|
|
|
|
|
// Unset fullscreen state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::FullScreen);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(fullScreenChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->isFullScreen(), false);
|
|
|
|
|
QCOMPARE(window->isRequestedFullScreen(), false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testKeepBelow()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that keep below state can be toggled and its changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set keep below.
|
|
|
|
|
QSignalSpy keepBelowChangedSpy(window, &Window::keepBelowChanged);
|
|
|
|
|
window->setKeepBelow(true);
|
|
|
|
|
QCOMPARE(keepBelowChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->keepBelow());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::KeepBelow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset keep below.
|
|
|
|
|
window->setKeepBelow(false);
|
|
|
|
|
QCOMPARE(keepBelowChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->keepBelow());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::KeepBelow));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyKeepBelow()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already with the keep below state set.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::KeepBelow, NET::KeepBelow);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->keepBelow());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testKeepAbove()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that keep above state can be toggled and its changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set keep above.
|
|
|
|
|
QSignalSpy keepAboveChangedSpy(window, &Window::keepAboveChanged);
|
|
|
|
|
window->setKeepAbove(true);
|
|
|
|
|
QCOMPARE(keepAboveChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->keepAbove());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::KeepAbove);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset keep above.
|
|
|
|
|
window->setKeepAbove(false);
|
|
|
|
|
QCOMPARE(keepAboveChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->keepAbove());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::KeepAbove));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyKeepAbove()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already with the keep above state set.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::KeepAbove, NET::KeepAbove);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->keepAbove());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testMinimized()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be minimized/unminimized and its changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Minimize.
|
|
|
|
|
QSignalSpy minimizedChangedSpy(window, &Window::minimizedChanged);
|
|
|
|
|
window->setMinimized(true);
|
|
|
|
|
QCOMPARE(minimizedChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->isMinimized());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::Hidden);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unminimize.
|
|
|
|
|
window->setMinimized(false);
|
|
|
|
|
QCOMPARE(minimizedChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->isMinimized());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-10 21:29:12 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::Hidden));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallyMinimized()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already in the minimized state.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_wm_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_wm_hints_set_iconic(&hints);
|
|
|
|
|
xcb_icccm_set_wm_hints(c.get(), windowId, &hints);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->isMinimized());
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::Hidden);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestMinimized()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can set the minimized state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set minimized state.
|
|
|
|
|
{
|
|
|
|
|
xcb_client_message_event_t event;
|
|
|
|
|
event.response_type = XCB_CLIENT_MESSAGE;
|
|
|
|
|
event.format = 32;
|
|
|
|
|
event.sequence = 0;
|
|
|
|
|
event.window = window->window();
|
|
|
|
|
event.type = atoms->wm_change_state;
|
|
|
|
|
event.data.data32[0] = XCB_ICCCM_WM_STATE_ICONIC;
|
|
|
|
|
event.data.data32[1] = 0;
|
|
|
|
|
event.data.data32[2] = 0;
|
|
|
|
|
event.data.data32[3] = 0;
|
|
|
|
|
event.data.data32[4] = 0;
|
|
|
|
|
|
|
|
|
|
xcb_send_event(c.get(), 0, kwinApp()->x11RootWindow(),
|
|
|
|
|
XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
|
|
|
|
|
reinterpret_cast<const char *>(&event));
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy minimizedChangedSpy(window, &Window::minimizedChanged);
|
|
|
|
|
QVERIFY(minimizedChangedSpy.wait());
|
|
|
|
|
QVERIFY(window->isMinimized());
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-14 21:06:06 +00:00
|
|
|
|
void X11WindowTest::testSkipSwitcher()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that skip switcher changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set skip switcher.
|
|
|
|
|
QSignalSpy skipSwitcherChangedSpy(window, &Window::skipSwitcherChanged);
|
|
|
|
|
window->setSkipSwitcher(true);
|
|
|
|
|
QCOMPARE(skipSwitcherChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->skipSwitcher());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::SkipSwitcher);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset skip switcher.
|
|
|
|
|
window->setSkipSwitcher(false);
|
|
|
|
|
QCOMPARE(skipSwitcherChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->skipSwitcher());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::SkipSwitcher));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallySkipSwitcher()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already with the skip switcher state set.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipSwitcher, NET::SkipSwitcher);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->skipSwitcher());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestSkipSwitcher()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can change the skip switcher state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set the skip switcher state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipSwitcher, NET::SkipSwitcher);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy skipSwitcherChangedSpy(window, &Window::skipSwitcherChanged);
|
|
|
|
|
QVERIFY(skipSwitcherChangedSpy.wait());
|
|
|
|
|
QVERIFY(window->skipSwitcher());
|
|
|
|
|
|
|
|
|
|
// Unset the skip switcher state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::SkipSwitcher);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(skipSwitcherChangedSpy.wait());
|
|
|
|
|
QVERIFY(!window->skipSwitcher());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testSkipPager()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that skip pager changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set skip pager.
|
|
|
|
|
QSignalSpy skipPagerChangedSpy(window, &Window::skipPagerChanged);
|
|
|
|
|
window->setSkipPager(true);
|
|
|
|
|
QCOMPARE(skipPagerChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->skipPager());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::SkipPager);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset skip pager.
|
|
|
|
|
window->setSkipPager(false);
|
|
|
|
|
QCOMPARE(skipPagerChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->skipPager());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::SkipPager));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallySkipPager()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already with the skip pager state set.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipPager, NET::SkipPager);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->skipPager());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestSkipPager()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can change the skip pager state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set the skip pager state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipPager, NET::SkipPager);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy skipPagerChangedSpy(window, &Window::skipPagerChanged);
|
|
|
|
|
QVERIFY(skipPagerChangedSpy.wait());
|
|
|
|
|
QVERIFY(window->skipPager());
|
|
|
|
|
|
|
|
|
|
// Unset the skip pager state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::SkipPager);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(skipPagerChangedSpy.wait());
|
|
|
|
|
QVERIFY(!window->skipPager());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testSkipTaskbar()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that skip taskbar changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set skip taskbar.
|
|
|
|
|
QSignalSpy skipTaskbarChangedSpy(window, &Window::skipTaskbarChanged);
|
|
|
|
|
window->setSkipTaskbar(true);
|
|
|
|
|
QCOMPARE(skipTaskbarChangedSpy.count(), 1);
|
|
|
|
|
QVERIFY(window->skipTaskbar());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(winInfo.state() & NET::SkipTaskbar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset skip taskbar.
|
|
|
|
|
window->setSkipTaskbar(false);
|
|
|
|
|
QCOMPARE(skipTaskbarChangedSpy.count(), 2);
|
|
|
|
|
QVERIFY(!window->skipTaskbar());
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-14 21:06:06 +00:00
|
|
|
|
NETWinInfo winInfo(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
QVERIFY(!(winInfo.state() & NET::SkipTaskbar));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitiallySkipTaskbar()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown already with the skip taskbar state set.
|
|
|
|
|
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipTaskbar, NET::SkipTaskbar);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->skipTaskbar());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testRequestSkipTaskbar()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can change the skip taskbar state.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set the skip taskbar state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::SkipTaskbar, NET::SkipTaskbar);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy skipTaskbarChangedSpy(window, &Window::skipTaskbarChanged);
|
|
|
|
|
QVERIFY(skipTaskbarChangedSpy.wait());
|
|
|
|
|
QVERIFY(window->skipTaskbar());
|
|
|
|
|
|
|
|
|
|
// Unset the skip taskbar state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::SkipTaskbar);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(skipTaskbarChangedSpy.wait());
|
|
|
|
|
QVERIFY(!window->skipTaskbar());
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-15 19:23:31 +00:00
|
|
|
|
void X11WindowTest::testOpacity()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that _NET_WM_WINDOW_OPACITY is properly sync'ed with Window::opacity().
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::WM2Opacity);
|
|
|
|
|
info.setOpacityF(0.5);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->opacity(), 0.5);
|
|
|
|
|
|
|
|
|
|
// Change the opacity.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::Properties(), NET::WM2Opacity);
|
|
|
|
|
info.setOpacityF(0.8);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy opacityChangedSpy(window, &Window::opacityChanged);
|
|
|
|
|
QVERIFY(opacityChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->opacity(), 0.8);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-15 20:45:07 +00:00
|
|
|
|
void X11WindowTest::testDesktop_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<uint>("desktopId");
|
|
|
|
|
|
|
|
|
|
QTest::addRow("first") << uint(1);
|
|
|
|
|
QTest::addRow("second") << uint(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testDesktop()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that desktop changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Send the window to the given desktop.
|
|
|
|
|
QFETCH(uint, desktopId);
|
|
|
|
|
VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId);
|
|
|
|
|
window->setDesktops({desktop});
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-15 20:45:07 +00:00
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
QCOMPARE(info.desktop(), desktopId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitialDesktop_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<uint>("desktopId");
|
|
|
|
|
|
|
|
|
|
QTest::addRow("first") << uint(1);
|
|
|
|
|
QTest::addRow("second") << uint(2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitialDesktop()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a window can be shown initially on a particular virtual desktop.
|
|
|
|
|
|
|
|
|
|
QFETCH(uint, desktopId);
|
|
|
|
|
VirtualDesktop *desktop = VirtualDesktopManager::self()->desktopForX11Id(desktopId);
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &desktopId](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(desktopId);
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(window->desktops(), (QList<VirtualDesktop *>{desktop}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testChangeDesktop()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the client can send its window to another virtual desktop.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Send the window to another desktop.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(2);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy desktopsChangedSpy(window, &Window::desktopsChanged);
|
|
|
|
|
QVERIFY(desktopsChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->desktops(), (QList<VirtualDesktop *>{VirtualDesktopManager::self()->desktopForX11Id(2)}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testOnAllDesktops()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that desktop changes are propagated to the client.
|
|
|
|
|
|
|
|
|
|
VirtualDesktop *activeDesktop = VirtualDesktopManager::self()->currentDesktop();
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
QCOMPARE(info.desktop(), activeDesktop->x11DesktopNumber());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set the window on all desktops.
|
|
|
|
|
window->setOnAllDesktops(true);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-15 20:45:07 +00:00
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
QCOMPARE(info.desktop(), -1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unset the window on all desktops.
|
|
|
|
|
window->setOnAllDesktops(false);
|
|
|
|
|
|
|
|
|
|
{
|
2024-03-16 13:04:01 +00:00
|
|
|
|
Xcb::sync();
|
2024-03-15 20:45:07 +00:00
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
QCOMPARE(info.desktop(), activeDesktop->x11DesktopNumber());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testInitialOnAllDesktops()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can show a window, which is already on all desktops.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(-1);
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(window->isOnAllDesktops());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testChangeOnAllDesktops()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can set/unset its window on all desktops.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Set the window on all desktops.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(-1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy desktopsChangedSpy(window, &Window::desktopsChanged);
|
|
|
|
|
QVERIFY(desktopsChangedSpy.wait());
|
|
|
|
|
QVERIFY(window->isOnAllDesktops());
|
|
|
|
|
|
|
|
|
|
// Unset the window on all desktops.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), window->window(), kwinApp()->x11RootWindow(), NET::WMDesktop, NET::Properties2());
|
|
|
|
|
info.setDesktop(1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(desktopsChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->desktops(), (QList<VirtualDesktop *>{VirtualDesktopManager::self()->desktopForX11Id(1)}));
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-16 11:22:42 +00:00
|
|
|
|
void X11WindowTest::testNetWmKeyboardMove()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate a keyboard interactive move operation.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::KeyboardMove, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveMove());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::keyboardKeyPressed(KEY_RIGHT, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_RIGHT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
|
|
|
|
|
// Finish the interactive move.
|
|
|
|
|
Test::keyboardKeyPressed(KEY_ENTER, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_ENTER, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmKeyboardMoveCancel()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate a keyboard interactive move operation and then cancel it.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::KeyboardMove, XCB_BUTTON_INDEX_ANY);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveMove());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::keyboardKeyPressed(KEY_RIGHT, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_RIGHT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
|
|
|
|
|
// Cancel the interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(interactiveMoveResizeFinishedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmKeyboardResize()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate a keyboard interactive resize operation.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive resize.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::KeyboardSize, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right, the frame geometry will be updated some time later.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::keyboardKeyPressed(KEY_RIGHT, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_RIGHT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0));
|
|
|
|
|
|
|
|
|
|
// Finish the interactive move.
|
|
|
|
|
Test::keyboardKeyPressed(KEY_ENTER, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_ENTER, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmKeyboardResizeCancel()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate a keyboard interactive resize operation and then cancel it.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive resize.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::KeyboardSize, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right, the frame geometry will be updated some time later.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::keyboardKeyPressed(KEY_RIGHT, timestamp++);
|
|
|
|
|
Test::keyboardKeyReleased(KEY_RIGHT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(0, 0, 8, 0));
|
|
|
|
|
|
|
|
|
|
// Cancel the interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width()), Xcb::toXNative(window->y() + window->height()), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(interactiveMoveResizeFinishedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonMove()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate an interactive move operation controlled by the pointer.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveMove());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(8, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
|
|
|
|
|
// Finish the interactive move.
|
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonMoveNotPressed()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that an interactive move operation won't be started if the specified button is not pressed.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QVERIFY(!interactiveMoveResizeStartedSpy.wait(10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonMoveCancel()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate an interactive move operation controlled by the pointer and then cancel it.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
|
|
|
auto releaseButton = qScopeGuard([×tamp]() {
|
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
|
|
|
});
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::Move, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveMove());
|
|
|
|
|
|
|
|
|
|
// Move the window to the right.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(8, 0), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.translated(8, 0));
|
|
|
|
|
|
|
|
|
|
// Cancel the interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x() + window->width() / 2), Xcb::toXNative(window->y() + window->height() / 2), NET::MoveResizeCancel, XCB_BUTTON_INDEX_ANY);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(interactiveMoveResizeFinishedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonSize_data()
|
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<Gravity>("gravity");
|
|
|
|
|
QTest::addColumn<NET::Direction>("direction");
|
|
|
|
|
|
|
|
|
|
QTest::addRow("top-left") << Gravity::TopLeft << NET::Direction::TopLeft;
|
|
|
|
|
QTest::addRow("top") << Gravity::Top << NET::Direction::Top;
|
|
|
|
|
QTest::addRow("top-right") << Gravity::TopRight << NET::Direction::TopRight;
|
|
|
|
|
QTest::addRow("right") << Gravity::Right << NET::Direction::Right;
|
|
|
|
|
QTest::addRow("bottom-right") << Gravity::BottomRight << NET::Direction::BottomRight;
|
|
|
|
|
QTest::addRow("bottom") << Gravity::Bottom << NET::Direction::Bottom;
|
|
|
|
|
QTest::addRow("bottom-left") << Gravity::BottomLeft << NET::Direction::BottomLeft;
|
|
|
|
|
QTest::addRow("left") << Gravity::Left << NET::Direction::Left;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QPointF directionToVector(NET::Direction direction, const QSizeF &size)
|
|
|
|
|
{
|
|
|
|
|
switch (direction) {
|
|
|
|
|
case NET::Direction::TopLeft:
|
|
|
|
|
return QPointF(-size.width(), -size.height());
|
|
|
|
|
case NET::Direction::Top:
|
|
|
|
|
return QPointF(0, -size.height());
|
|
|
|
|
case NET::Direction::TopRight:
|
|
|
|
|
return QPointF(size.width(), -size.height());
|
|
|
|
|
case NET::Direction::Right:
|
|
|
|
|
return QPointF(size.width(), 0);
|
|
|
|
|
case NET::Direction::BottomRight:
|
|
|
|
|
return QPointF(size.width(), size.height());
|
|
|
|
|
case NET::Direction::Bottom:
|
|
|
|
|
return QPointF(0, size.height());
|
|
|
|
|
case NET::Direction::BottomLeft:
|
|
|
|
|
return QPointF(-size.width(), size.height());
|
|
|
|
|
case NET::Direction::Left:
|
|
|
|
|
return QPointF(-size.width(), 0);
|
|
|
|
|
default:
|
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static QRectF expandRect(const QRectF &rect, NET::Direction direction, const QSizeF &amount)
|
|
|
|
|
{
|
|
|
|
|
switch (direction) {
|
|
|
|
|
case NET::Direction::TopLeft:
|
|
|
|
|
return rect.adjusted(-amount.width(), -amount.height(), 0, 0);
|
|
|
|
|
case NET::Direction::Top:
|
|
|
|
|
return rect.adjusted(0, -amount.height(), 0, 0);
|
|
|
|
|
case NET::Direction::TopRight:
|
|
|
|
|
return rect.adjusted(0, -amount.height(), amount.width(), 0);
|
|
|
|
|
case NET::Direction::Right:
|
|
|
|
|
return rect.adjusted(0, 0, amount.width(), 0);
|
|
|
|
|
case NET::Direction::BottomRight:
|
|
|
|
|
return rect.adjusted(0, 0, amount.width(), amount.height());
|
|
|
|
|
case NET::Direction::Bottom:
|
|
|
|
|
return rect.adjusted(0, 0, 0, amount.height());
|
|
|
|
|
case NET::Direction::BottomLeft:
|
|
|
|
|
return rect.adjusted(-amount.width(), 0, 0, amount.height());
|
|
|
|
|
case NET::Direction::Left:
|
|
|
|
|
return rect.adjusted(-amount.width(), 0, 0, 0);
|
|
|
|
|
default:
|
|
|
|
|
Q_UNREACHABLE();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonSize()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can initiate an interactive move operation controlled by the pointer.
|
|
|
|
|
|
|
|
|
|
QFETCH(NET::Direction, direction);
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
const QPointF initialPointer = window->frameGeometry().center() + directionToVector(direction, originalGeometry.size() * 0.5);
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
|
|
|
Test::pointerMotion(initialPointer, timestamp++);
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(initialPointer.x()), Xcb::toXNative(initialPointer.y()), direction, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
|
|
|
|
QTEST(window->interactiveMoveResizeGravity(), "gravity");
|
|
|
|
|
|
|
|
|
|
// Resize the window a tiny bit.
|
|
|
|
|
Test::pointerMotionRelative(directionToVector(direction, QSizeF(8, 8)), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), expandRect(originalGeometry, direction, QSizeF(8, 8)));
|
|
|
|
|
|
|
|
|
|
// Finish the interactive move.
|
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(window->frameGeometry(), expandRect(originalGeometry, direction, QSizeF(8, 8)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonSizeNotPressed()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that an interactive siize operation won't be started if the specified button is not pressed.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive move.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::TopLeft, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QVERIFY(!interactiveMoveResizeStartedSpy.wait(10));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testNetWmButtonSizeCancel()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a client can start an interactive resize and then cancel it.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *window = createWindow(c.get(), QRect(100, 100, 100, 200));
|
|
|
|
|
|
|
|
|
|
// Request interactive resize.
|
|
|
|
|
const QRectF originalGeometry = window->frameGeometry();
|
|
|
|
|
quint32 timestamp = 0;
|
|
|
|
|
Test::pointerButtonPressed(BTN_LEFT, timestamp++);
|
|
|
|
|
auto releaseButton = qScopeGuard([×tamp]() {
|
|
|
|
|
Test::pointerButtonReleased(BTN_LEFT, timestamp++);
|
|
|
|
|
});
|
|
|
|
|
Test::pointerMotion(originalGeometry.topLeft(), timestamp++);
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::TopLeft, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &X11Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &X11Window::interactiveMoveResizeFinished);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &X11Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &X11Window::frameGeometryChanged);
|
|
|
|
|
QVERIFY(interactiveMoveResizeStartedSpy.wait());
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
|
|
|
|
|
|
|
|
|
// Resize the window a tiny bit.
|
|
|
|
|
Test::pointerMotionRelative(QPointF(-8, -8), timestamp++);
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry.adjusted(-8, -8, 0, 0));
|
|
|
|
|
|
|
|
|
|
// Cancel the interactive resize.
|
|
|
|
|
{
|
|
|
|
|
NETRootInfo root(c.get(), NET::Properties());
|
|
|
|
|
root.moveResizeRequest(window->window(), Xcb::toXNative(window->x()), Xcb::toXNative(window->y()), NET::MoveResizeCancel, XCB_BUTTON_INDEX_1);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(interactiveMoveResizeFinishedSpy.wait());
|
|
|
|
|
QCOMPARE(window->frameGeometry(), originalGeometry);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testMinimumSize()
|
2020-02-14 11:59:58 +00:00
|
|
|
|
{
|
|
|
|
|
// This test verifies that the minimum size constraint is correctly applied.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2020-02-14 11:59:58 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
|
|
|
|
xcb_icccm_size_hints_set_min_size(&hints, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QVERIFY(window->isDecorated());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), nullptr);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), window);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2023-02-19 23:35:44 +00:00
|
|
|
|
const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2024-03-14 19:44:12 +00:00
|
|
|
|
const qreal scale = kwinApp()->xwaylandScale();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Left);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Right);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Right);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2022-05-16 20:13:39 +00:00
|
|
|
|
// whilst X11 window size goes through scale, the increment is a logical value kwin side
|
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale + 8);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Up);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Down);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Down);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale + 8);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), nullptr);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2023-03-13 19:21:11 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(window, &X11Window::closed);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_destroy_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testMaximumSize()
|
2020-02-14 11:59:58 +00:00
|
|
|
|
{
|
|
|
|
|
// This test verifies that the maximum size constraint is correctly applied.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2020-02-14 11:59:58 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
|
|
|
|
xcb_icccm_size_hints_set_max_size(&hints, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.last().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QVERIFY(window->isDecorated());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QSignalSpy interactiveMoveResizeStartedSpy(window, &Window::interactiveMoveResizeStarted);
|
|
|
|
|
QSignalSpy interactiveMoveResizeSteppedSpy(window, &Window::interactiveMoveResizeStepped);
|
|
|
|
|
QSignalSpy interactiveMoveResizeFinishedSpy(window, &Window::interactiveMoveResizeFinished);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QSignalSpy frameGeometryChangedSpy(window, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), nullptr);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), window);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeStartedSpy.count(), 1);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2023-02-19 23:35:44 +00:00
|
|
|
|
const QPointF cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2024-03-14 19:44:12 +00:00
|
|
|
|
const qreal scale = kwinApp()->xwaylandScale();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Right);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Left);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QVERIFY(!interactiveMoveResizeSteppedSpy.wait(10));
|
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 0);
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Left);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().width(), 100 / scale - 8);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Down);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Up);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 1);
|
2023-01-11 21:05:45 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(10));
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Up);
|
|
|
|
|
window->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8));
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeSteppedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2022-05-16 20:13:39 +00:00
|
|
|
|
QCOMPARE(window->clientSize().height(), 200 / scale - 8);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 0);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->keyPressEvent(Qt::Key_Enter);
|
2023-02-18 11:38:08 +00:00
|
|
|
|
QCOMPARE(interactiveMoveResizeFinishedSpy.count(), 1);
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QCOMPARE(workspace()->moveResizeWindow(), nullptr);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2023-03-13 19:21:11 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(window, &X11Window::closed);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_destroy_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testTrimCaption_data()
|
Fix captions with non-BMP characters
KWin replaces any non-printable character with a space. This check does not
handle surrogate pairs correctly. Additionally, translators sometimes insert
non-printable soft-hyphens into titles, which also cause KWin to display a
space instead.
This code adds the missing surrogate handling, and (to fix both issues), also
removes non-printable characters instead of replacing them with a space.
Also moved the changed test after these changes, so that changes in non-
printable characters do not cause unneeded redraws.
Unit tests adapted by Vlad Zagorodniy.
Test Plan:
kwrite /tmp/Test😣.txt shows correct title. I also tested actual non-printable
characters, such as 0x1A, and these are correctly omitted.
BUG: 376813
FIXED-IN: 5.15.5
Reviewers: #kwin, zzag
Reviewed By: #kwin, zzag
Subscribers: zzag, grasslin, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19052
2019-04-26 08:52:57 +00:00
|
|
|
|
{
|
|
|
|
|
QTest::addColumn<QByteArray>("originalTitle");
|
|
|
|
|
QTest::addColumn<QByteArray>("expectedTitle");
|
|
|
|
|
|
|
|
|
|
QTest::newRow("simplified")
|
|
|
|
|
<< QByteArrayLiteral("Was tun, wenn Schüler Autismus haben?\342\200\250\342\200\250\342\200\250 – Marlies Hübner - Mozilla Firefox")
|
|
|
|
|
<< QByteArrayLiteral("Was tun, wenn Schüler Autismus haben? – Marlies Hübner - Mozilla Firefox");
|
|
|
|
|
|
|
|
|
|
QTest::newRow("with emojis")
|
|
|
|
|
<< QByteArrayLiteral("\bTesting non\302\255printable:\177, emoij:\360\237\230\203, non-characters:\357\277\276")
|
|
|
|
|
<< QByteArrayLiteral("Testing nonprintable:, emoij:\360\237\230\203, non-characters:");
|
|
|
|
|
}
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testTrimCaption()
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
|
|
|
|
// this test verifies that caption is properly trimmed
|
|
|
|
|
|
|
|
|
|
// create an xcb window
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2016-10-31 14:50:14 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2016-10-31 14:50:14 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
NETWinInfo winInfo(c.get(), windowId, rootWindow(), NET::Properties(), NET::Properties2());
|
Fix captions with non-BMP characters
KWin replaces any non-printable character with a space. This check does not
handle surrogate pairs correctly. Additionally, translators sometimes insert
non-printable soft-hyphens into titles, which also cause KWin to display a
space instead.
This code adds the missing surrogate handling, and (to fix both issues), also
removes non-printable characters instead of replacing them with a space.
Also moved the changed test after these changes, so that changes in non-
printable characters do not cause unneeded redraws.
Unit tests adapted by Vlad Zagorodniy.
Test Plan:
kwrite /tmp/Test😣.txt shows correct title. I also tested actual non-printable
characters, such as 0x1A, and these are correctly omitted.
BUG: 376813
FIXED-IN: 5.15.5
Reviewers: #kwin, zzag
Reviewed By: #kwin, zzag
Subscribers: zzag, grasslin, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19052
2019-04-26 08:52:57 +00:00
|
|
|
|
QFETCH(QByteArray, originalTitle);
|
|
|
|
|
winInfo.setName(originalTitle);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// we should get a window for it
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2016-10-31 14:50:14 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->window(), windowId);
|
Fix captions with non-BMP characters
KWin replaces any non-printable character with a space. This check does not
handle surrogate pairs correctly. Additionally, translators sometimes insert
non-printable soft-hyphens into titles, which also cause KWin to display a
space instead.
This code adds the missing surrogate handling, and (to fix both issues), also
removes non-printable characters instead of replacing them with a space.
Also moved the changed test after these changes, so that changes in non-
printable characters do not cause unneeded redraws.
Unit tests adapted by Vlad Zagorodniy.
Test Plan:
kwrite /tmp/Test😣.txt shows correct title. I also tested actual non-printable
characters, such as 0x1A, and these are correctly omitted.
BUG: 376813
FIXED-IN: 5.15.5
Reviewers: #kwin, zzag
Reviewed By: #kwin, zzag
Subscribers: zzag, grasslin, kwin
Tags: #kwin
Differential Revision: https://phabricator.kde.org/D19052
2019-04-26 08:52:57 +00:00
|
|
|
|
QFETCH(QByteArray, expectedTitle);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(window->caption(), QString::fromUtf8(expectedTitle));
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
2023-03-13 19:21:11 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(window, &X11Window::closed);
|
2016-10-31 14:50:14 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_destroy_window(c.get(), windowId);
|
2016-10-31 14:50:14 +00:00
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testFullscreenLayerWithActiveWaylandWindow()
|
2017-07-23 09:50:58 +00:00
|
|
|
|
{
|
|
|
|
|
// this test verifies that an X11 fullscreen window does not stay in the active layer
|
|
|
|
|
// when a Wayland window is active, see BUG: 375759
|
2022-07-11 10:41:15 +00:00
|
|
|
|
QCOMPARE(workspace()->outputs().count(), 1);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// first create an X11 window
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2017-07-23 09:50:58 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2017-07-23 09:50:58 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// we should get a window for it
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->window(), windowId);
|
|
|
|
|
QVERIFY(!window->isFullScreen());
|
|
|
|
|
QVERIFY(window->isActive());
|
|
|
|
|
QCOMPARE(window->layer(), NormalLayer);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isFullScreen());
|
|
|
|
|
QCOMPARE(window->layer(), ActiveLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// now let's open a Wayland window
|
2022-08-01 21:29:02 +00:00
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
|
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
|
|
|
auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(waylandWindow);
|
|
|
|
|
QVERIFY(waylandWindow->isActive());
|
|
|
|
|
QCOMPARE(waylandWindow->layer(), NormalLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(window->layer(), NormalLayer);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// now activate fullscreen again
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(window);
|
|
|
|
|
QTRY_VERIFY(window->isActive());
|
|
|
|
|
QCOMPARE(window->layer(), ActiveLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(waylandWindow);
|
|
|
|
|
QTRY_VERIFY(waylandWindow->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// back to x window
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(window);
|
|
|
|
|
QTRY_VERIFY(window->isActive());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// remove fullscreen
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isFullScreen());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
workspace()->slotWindowFullScreen();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isFullScreen());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// and fullscreen again
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isFullScreen());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(waylandWindow);
|
|
|
|
|
QTRY_VERIFY(waylandWindow->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// back to X11 window
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(window);
|
|
|
|
|
QTRY_VERIFY(window->isActive());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// remove fullscreen
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window->isFullScreen());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
workspace()->slotWindowFullScreen();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!window->isFullScreen());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// and fullscreen through X API
|
2022-08-01 21:29:02 +00:00
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
2017-07-23 18:02:57 +00:00
|
|
|
|
info.setState(NET::FullScreen, NET::FullScreen);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
NETRootInfo rootInfo(c.get(), NET::Properties());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
rootInfo.setActiveWindow(windowId, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_flush(c.get());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QTRY_VERIFY(window->isFullScreen());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), window);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(waylandWindow);
|
|
|
|
|
QTRY_VERIFY(waylandWindow->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandWindow);
|
|
|
|
|
QCOMPARE(window->layer(), NormalLayer);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
2017-07-23 09:50:58 +00:00
|
|
|
|
// close the window
|
|
|
|
|
shellSurface.reset();
|
|
|
|
|
surface.reset();
|
2023-04-21 20:28:48 +00:00
|
|
|
|
QVERIFY(Test::waitForWindowClosed(waylandWindow));
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QTRY_VERIFY(window->isActive());
|
|
|
|
|
QCOMPARE(window->layer(), ActiveLayer);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-07-23 09:50:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testFocusInWithWaylandLastActiveWindow()
|
2017-07-23 14:18:09 +00:00
|
|
|
|
{
|
2022-04-23 08:33:23 +00:00
|
|
|
|
// this test verifies that Workspace::allowWindowActivation does not crash if last client was a Wayland client
|
2017-07-23 14:18:09 +00:00
|
|
|
|
|
|
|
|
|
// create an X11 window
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2017-07-23 14:18:09 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2017-07-23 14:18:09 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// we should get a window for it
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2017-07-23 14:18:09 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->window(), windowId);
|
|
|
|
|
QVERIFY(window->isActive());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
|
|
|
|
|
// create Wayland window
|
2022-08-01 21:29:02 +00:00
|
|
|
|
std::unique_ptr<KWayland::Client::Surface> surface(Test::createSurface());
|
|
|
|
|
std::unique_ptr<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.get()));
|
|
|
|
|
auto waylandWindow = Test::renderAndWaitForShown(surface.get(), QSize(100, 50), Qt::blue);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(waylandWindow);
|
|
|
|
|
QVERIFY(waylandWindow->isActive());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
// activate no window
|
2022-04-23 08:33:23 +00:00
|
|
|
|
workspace()->setActiveWindow(nullptr);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(!waylandWindow->isActive());
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QVERIFY(!workspace()->activeWindow());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
// and close Wayland window again
|
|
|
|
|
shellSurface.reset();
|
|
|
|
|
surface.reset();
|
2023-04-21 20:28:48 +00:00
|
|
|
|
QVERIFY(Test::waitForWindowClosed(waylandWindow));
|
2017-07-23 14:18:09 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// and try to activate the x11 window through X11 api
|
2022-08-01 21:29:02 +00:00
|
|
|
|
const auto cookie = xcb_set_input_focus_checked(c.get(), XCB_INPUT_FOCUS_NONE, windowId, XCB_CURRENT_TIME);
|
|
|
|
|
auto error = xcb_request_check(c.get(), cookie);
|
2017-07-23 14:18:09 +00:00
|
|
|
|
QVERIFY(!error);
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// this accesses m_lastActiveWindow on trying to activate
|
|
|
|
|
QTRY_VERIFY(window->isActive());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-07-23 14:18:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testCaptionChanges()
|
2017-08-13 14:52:14 +00:00
|
|
|
|
{
|
|
|
|
|
// verifies that caption is updated correctly when the X11 window updates it
|
|
|
|
|
// BUG: 383444
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2017-08-13 14:52:14 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2017-08-13 14:52:14 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
NETWinInfo info(c.get(), windowId, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
2017-08-13 14:52:14 +00:00
|
|
|
|
info.setName("foo");
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-08-13 14:52:14 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// we should get a window for it
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->window(), windowId);
|
|
|
|
|
QCOMPARE(window->caption(), QStringLiteral("foo"));
|
2017-08-13 14:52:14 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QSignalSpy captionChangedSpy(window, &X11Window::captionChanged);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
info.setName("bar");
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_flush(c.get());
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(captionChangedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(window->caption(), QStringLiteral("bar"));
|
2017-08-13 14:52:14 +00:00
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
2023-03-13 19:21:11 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(window, &X11Window::closed);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_unmap_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_destroy_window(c.get(), windowId);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testCaptionWmName()
|
2017-08-01 19:28:36 +00:00
|
|
|
|
{
|
|
|
|
|
// this test verifies that a caption set through WM_NAME is read correctly
|
|
|
|
|
|
|
|
|
|
// open glxgears as that one only uses WM_NAME
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowAddedSpy(workspace(), &Workspace::windowAdded);
|
2017-08-01 19:28:36 +00:00
|
|
|
|
|
|
|
|
|
QProcess glxgears;
|
2020-09-10 07:15:31 +00:00
|
|
|
|
glxgears.setProgram(QStringLiteral("glxgears"));
|
|
|
|
|
glxgears.start();
|
2017-08-01 19:28:36 +00:00
|
|
|
|
QVERIFY(glxgears.waitForStarted());
|
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QVERIFY(windowAddedSpy.wait());
|
|
|
|
|
QCOMPARE(windowAddedSpy.count(), 1);
|
2023-03-21 17:47:58 +00:00
|
|
|
|
QCOMPARE(workspace()->windows().count(), 1);
|
|
|
|
|
Window *glxgearsWindow = workspace()->windows().first();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(glxgearsWindow->caption(), QStringLiteral("glxgears"));
|
2017-08-01 19:28:36 +00:00
|
|
|
|
|
|
|
|
|
glxgears.terminate();
|
|
|
|
|
QVERIFY(glxgears.waitForFinished());
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testFullscreenWindowGroups()
|
2018-01-06 09:45:44 +00:00
|
|
|
|
{
|
|
|
|
|
// this test creates an X11 window and puts it to full screen
|
|
|
|
|
// then a second window is created which is in the same window group
|
|
|
|
|
// BUG: 388310
|
|
|
|
|
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2018-01-06 09:45:44 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2018-01-06 09:45:44 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2018-01-06 09:45:44 +00:00
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->window(), windowId);
|
|
|
|
|
QCOMPARE(window->isActive(), true);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(window->isFullScreen(), false);
|
|
|
|
|
QCOMPARE(window->layer(), NormalLayer);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
workspace()->slotWindowFullScreen();
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QCOMPARE(window->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->layer(), ActiveLayer);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
|
|
|
|
|
// now let's create a second window
|
|
|
|
|
windowCreatedSpy.clear();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t w2 = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
|
2018-01-06 09:45:44 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints2;
|
|
|
|
|
memset(&hints2, 0, sizeof(hints2));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints2, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints2, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), w2, &hints2);
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
xcb_map_window(c.get(), w2);
|
|
|
|
|
xcb_flush(c.get());
|
2018-01-06 09:45:44 +00:00
|
|
|
|
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window2 = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window2);
|
|
|
|
|
QVERIFY(window != window2);
|
|
|
|
|
QCOMPARE(window2->window(), w2);
|
|
|
|
|
QCOMPARE(window2->isActive(), true);
|
|
|
|
|
QCOMPARE(window2->group(), window->group());
|
|
|
|
|
// first window should be moved back to normal layer
|
|
|
|
|
QCOMPARE(window->isActive(), false);
|
|
|
|
|
QCOMPARE(window->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(window->layer(), NormalLayer);
|
2018-01-06 10:24:31 +00:00
|
|
|
|
|
|
|
|
|
// activating the fullscreen window again, should move it to active layer
|
2022-04-23 19:51:16 +00:00
|
|
|
|
workspace()->activateWindow(window);
|
|
|
|
|
QTRY_COMPARE(window->layer(), ActiveLayer);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testActivateFocusedWindow()
|
2020-06-16 08:03:25 +00:00
|
|
|
|
{
|
|
|
|
|
// The window manager may call XSetInputFocus() on a window that already has focus, in which
|
|
|
|
|
// case no FocusIn event will be generated and the window won't be marked as active. This test
|
|
|
|
|
// verifies that we handle that subtle case properly.
|
|
|
|
|
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr connection = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(connection.get()));
|
2020-06-16 08:03:25 +00:00
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2020-06-16 08:03:25 +00:00
|
|
|
|
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
|
|
|
|
|
|
|
|
|
// Create the first test window.
|
2022-08-01 21:29:02 +00:00
|
|
|
|
const xcb_window_t windowId1 = xcb_generate_id(connection.get());
|
|
|
|
|
xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId1, rootWindow(),
|
2020-06-16 08:03:25 +00:00
|
|
|
|
windowGeometry.x(), windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(), windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(connection.get(), windowId1, &hints);
|
|
|
|
|
xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId1,
|
2022-04-23 19:51:16 +00:00
|
|
|
|
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId1);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_map_window(connection.get(), windowId1);
|
|
|
|
|
xcb_flush(connection.get());
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window1 = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window1);
|
|
|
|
|
QCOMPARE(window1->window(), windowId1);
|
|
|
|
|
QCOMPARE(window1->isActive(), true);
|
2020-06-16 08:03:25 +00:00
|
|
|
|
|
|
|
|
|
// Create the second test window.
|
2022-08-01 21:29:02 +00:00
|
|
|
|
const xcb_window_t windowId2 = xcb_generate_id(connection.get());
|
|
|
|
|
xcb_create_window(connection.get(), XCB_COPY_FROM_PARENT, windowId2, rootWindow(),
|
2020-06-16 08:03:25 +00:00
|
|
|
|
windowGeometry.x(), windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(), windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(connection.get(), windowId2, &hints);
|
|
|
|
|
xcb_change_property(connection.get(), XCB_PROP_MODE_REPLACE, windowId2,
|
2022-04-23 19:51:16 +00:00
|
|
|
|
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId2);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_map_window(connection.get(), windowId2);
|
|
|
|
|
xcb_flush(connection.get());
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window2 = windowCreatedSpy.last().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window2);
|
|
|
|
|
QCOMPARE(window2->window(), windowId2);
|
|
|
|
|
QCOMPARE(window2->isActive(), true);
|
2020-06-16 08:03:25 +00:00
|
|
|
|
|
|
|
|
|
// When the second test window is destroyed, the window manager will attempt to activate the
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// next window in the focus chain, which is the first window.
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_set_input_focus(connection.get(), XCB_INPUT_FOCUS_POINTER_ROOT, windowId1, XCB_CURRENT_TIME);
|
|
|
|
|
xcb_destroy_window(connection.get(), windowId2);
|
|
|
|
|
xcb_flush(connection.get());
|
2023-04-21 20:28:48 +00:00
|
|
|
|
QVERIFY(Test::waitForWindowClosed(window2));
|
2022-04-23 19:51:16 +00:00
|
|
|
|
QVERIFY(window1->isActive());
|
2020-06-16 08:03:25 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the first test window.
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_destroy_window(connection.get(), windowId1);
|
|
|
|
|
xcb_flush(connection.get());
|
2023-04-21 20:28:48 +00:00
|
|
|
|
QVERIFY(Test::waitForWindowClosed(window1));
|
2020-06-16 08:03:25 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testReentrantMoveResize()
|
2020-07-20 19:33:19 +00:00
|
|
|
|
{
|
Rework async geometry updates
Window management features were written with synchronous geometry
updates in mind. Currently, this poses a big problem on Wayland because
geometry updates are done in asynchronous fashion there.
At the moment, geometry is updated in a so called pseudo-asynchronous
fashion, meaning that the frame geometry will be reset to the old value
once geometry updates are unblocked. The main drawback of this approach
is that it is too error prone, the data flow is hard to comprehend, etc.
It is worth noting that there is already a machinery to perform async
geometry which is used during interactive move/resize operations.
This change extends the move/resize geometry usage beyond interactive
move/resize to make asynchronous geometry updates less error prone and
easier to comprehend.
With the proposed solution, all geometry updates must be done on the
move/resize geometry first. After that, the new geometry is passed on to
the Client-specific implementation of moveResizeInternal().
To be more specific, the frameGeometry() returns the current frame
geometry, it is primarily useful only to the scene. If you want to move
or resize a window, you need to use moveResizeGeometry() because it
corresponds to the last requested frame geometry.
It is worth noting that the moveResizeGeometry() returns the desired
bounding geometry. The client may commit the xdg_toplevel surface with a
slightly smaller window geometry, for example to enforce a specific
aspect ratio. The client is not allowed to resize beyond the size as
indicated in moveResizeGeometry().
The data flow is very simple: moveResize() updates the move/resize
geometry and calls the client-specific implementation of the
moveResizeInternal() method. Based on whether a configure event is
needed, moveResizeInternal() will update the frameGeometry() either
immediately or after the client commits a new buffer.
Unfortunately, both the compositor and xdg-shell clients try to update
the window geometry. It means that it's possible to have conflicts
between the two. With this change, the compositor's move resize geometry
will be synced only if there are no pending configure events, meaning
that the user doesn't try to resize the window.
2021-04-30 18:26:09 +00:00
|
|
|
|
// This test verifies that calling moveResize() from a slot connected directly
|
2020-07-20 19:33:19 +00:00
|
|
|
|
// to the frameGeometryChanged() signal won't cause an infinite recursion.
|
|
|
|
|
|
|
|
|
|
// Create a test window.
|
2023-02-03 13:29:21 +00:00
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
2022-08-01 21:29:02 +00:00
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_window_t windowId = xcb_generate_id(c.get());
|
|
|
|
|
xcb_create_window(c.get(), XCB_COPY_FROM_PARENT, windowId, rootWindow(),
|
2020-07-20 19:33:19 +00:00
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_size_hints_t hints;
|
|
|
|
|
memset(&hints, 0, sizeof(hints));
|
|
|
|
|
xcb_icccm_size_hints_set_position(&hints, 1, windowGeometry.x(), windowGeometry.y());
|
|
|
|
|
xcb_icccm_size_hints_set_size(&hints, 1, windowGeometry.width(), windowGeometry.height());
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.get(), windowId, &hints);
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
xcb_map_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2020-07-20 19:33:19 +00:00
|
|
|
|
|
2022-04-23 08:33:23 +00:00
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::windowAdded);
|
2020-07-20 19:33:19 +00:00
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-23 19:51:16 +00:00
|
|
|
|
X11Window *window = windowCreatedSpy.first().first().value<X11Window *>();
|
|
|
|
|
QVERIFY(window);
|
|
|
|
|
QCOMPARE(window->pos(), QPoint(0, 0));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// Let's pretend that there is a script that really wants the window to be at (100, 100).
|
|
|
|
|
connect(window, &Window::frameGeometryChanged, this, [window]() {
|
2022-05-16 20:13:39 +00:00
|
|
|
|
window->moveResize(QRectF(QPointF(100, 100), window->size()));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Trigger the lambda above.
|
2022-04-23 19:51:16 +00:00
|
|
|
|
window->move(QPoint(40, 50));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
|
2022-04-23 19:51:16 +00:00
|
|
|
|
// Eventually, the window will end up at (100, 100).
|
|
|
|
|
QCOMPARE(window->pos(), QPoint(100, 100));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the test window.
|
2022-08-01 21:29:02 +00:00
|
|
|
|
xcb_destroy_window(c.get(), windowId);
|
|
|
|
|
xcb_flush(c.get());
|
2023-04-21 20:28:48 +00:00
|
|
|
|
QVERIFY(Test::waitForWindowClosed(window));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-19 21:31:16 +00:00
|
|
|
|
void X11WindowTest::testTransient()
|
|
|
|
|
{
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(child->isTransient());
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
QVERIFY(parent->hasTransient(child, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testGroupTransient()
|
|
|
|
|
{
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QCOMPARE(dialog->transientFor(), nullptr);
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
|
|
|
|
|
// The group transient should not act as transient for unrelated windows.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QVERIFY(!unrelated->hasTransient(dialog, true));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseTransient()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the parent window will be activated when a transient is closed.
|
|
|
|
|
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), child);
|
|
|
|
|
|
|
|
|
|
// Close the child.
|
|
|
|
|
QSignalSpy childClosedSpy(child, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), child->window());
|
|
|
|
|
xcb_destroy_window(c.get(), child->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(childClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseInactiveTransient()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the parent window will not be activated when an inactive transient is closed.
|
|
|
|
|
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), child);
|
|
|
|
|
|
|
|
|
|
// Show another window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
|
|
|
|
|
// Close the child.
|
|
|
|
|
QSignalSpy childClosedSpy(child, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), child->window());
|
|
|
|
|
xcb_destroy_window(c.get(), child->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(childClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseGroupTransient()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that when an active group transient is closed, the focus will be passed to one of its main windows.
|
|
|
|
|
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QCOMPARE(dialog->transientFor(), nullptr);
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
|
|
|
|
|
// Close the dialog.
|
|
|
|
|
QSignalSpy dialogClosedSpy(dialog, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), dialog->window());
|
|
|
|
|
xcb_destroy_window(c.get(), dialog->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(dialogClosedSpy.wait());
|
|
|
|
|
QVERIFY(workspace()->activeWindow() == leader || workspace()->activeWindow() == follower);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseInactiveGroupTransient()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that when an inactive group transient is closed, the focus will not be passed to one of its main windows.
|
|
|
|
|
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QCOMPARE(dialog->transientFor(), nullptr);
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
|
|
|
|
|
// Show another window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
|
|
|
|
|
// Close the dialog.
|
|
|
|
|
QSignalSpy dialogClosedSpy(dialog, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), dialog->window());
|
|
|
|
|
xcb_destroy_window(c.get(), dialog->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(dialogClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-18 21:28:07 +00:00
|
|
|
|
void X11WindowTest::testModal()
|
|
|
|
|
{
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(!child->isModal());
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), child->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(child, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(child->isModal());
|
|
|
|
|
|
|
|
|
|
// Unset modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), child->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(!child->isModal());
|
|
|
|
|
|
|
|
|
|
// Set modal state and try to activate the parent window, it should not succeed.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), child->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(child->isModal());
|
|
|
|
|
workspace()->activateWindow(parent);
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), child);
|
|
|
|
|
|
|
|
|
|
// It should be okay to activate an unrelated window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testGroupModal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that a dialog can be modal to the window group.
|
|
|
|
|
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), dialog->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(dialog, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(dialog->isModal());
|
|
|
|
|
|
|
|
|
|
// Unset modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), dialog->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::State(), NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(!dialog->isModal());
|
|
|
|
|
|
|
|
|
|
// Set modal state and try to activate other windows in the group, it should not succeed.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), dialog->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(dialog->isModal());
|
|
|
|
|
workspace()->activateWindow(leader);
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
workspace()->activateWindow(follower);
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
|
|
|
|
|
// It should be okay to activate an unrelated window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseModal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the parent window will be activated when an active modal dialog is closed.
|
|
|
|
|
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(!child->isModal());
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), child->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(child, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(child->isModal());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), child);
|
|
|
|
|
|
|
|
|
|
// Close the child.
|
|
|
|
|
QSignalSpy childClosedSpy(child, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), child->window());
|
|
|
|
|
xcb_destroy_window(c.get(), child->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(childClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), parent);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseInactiveModal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that the parent window will not be activated when an inactive modal dialog is closed.
|
|
|
|
|
|
|
|
|
|
// Create a parent and a child windows.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *parent = createWindow(c.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
X11Window *child = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &parent](xcb_window_t windowId) {
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, parent->window());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(!child->isModal());
|
|
|
|
|
QCOMPARE(child->transientFor(), parent);
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), child->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(child, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(child->isModal());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), child);
|
|
|
|
|
|
|
|
|
|
// Show another window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
|
|
|
|
|
// Close the child.
|
|
|
|
|
QSignalSpy childClosedSpy(child, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), child->window());
|
|
|
|
|
xcb_destroy_window(c.get(), child->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(childClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseGroupModal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that when an active modal group dialog is closed, the focus will be passed to one of its main windows.
|
|
|
|
|
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), dialog->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(dialog, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(dialog->isModal());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
|
|
|
|
|
// Close the dialog.
|
|
|
|
|
QSignalSpy dialogClosedSpy(dialog, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), dialog->window());
|
|
|
|
|
xcb_destroy_window(c.get(), dialog->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(dialogClosedSpy.wait());
|
|
|
|
|
QVERIFY(workspace()->activeWindow() == leader || workspace()->activeWindow() == follower);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void X11WindowTest::testCloseInactiveGroupModal()
|
|
|
|
|
{
|
|
|
|
|
// This test verifies that when an inactive modal group dialog is closed, the focus will not be passed to one of its main windows.
|
|
|
|
|
|
|
|
|
|
// Create the leader, a follower and a dialog window.
|
|
|
|
|
Test::XcbConnectionPtr c = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *leader = createWindow(c.get(), QRect(0, 0, 100, 200), [&c](xcb_window_t windowId) {
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &windowId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *follower = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
});
|
|
|
|
|
X11Window *dialog = createWindow(c.get(), QRect(0, 0, 100, 200), [&c, &leader](xcb_window_t windowId) {
|
|
|
|
|
const xcb_window_t leaderId = leader->window();
|
|
|
|
|
xcb_change_property(c.get(), XCB_PROP_MODE_REPLACE, windowId, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &leaderId);
|
|
|
|
|
xcb_icccm_set_wm_transient_for(c.get(), windowId, kwinApp()->x11RootWindow());
|
|
|
|
|
});
|
|
|
|
|
QVERIFY(dialog->isTransient());
|
|
|
|
|
QVERIFY(leader->hasTransient(dialog, true));
|
|
|
|
|
QVERIFY(follower->hasTransient(dialog, true));
|
|
|
|
|
|
|
|
|
|
// Set modal state.
|
|
|
|
|
{
|
|
|
|
|
NETWinInfo info(c.get(), dialog->window(), kwinApp()->x11RootWindow(), NET::WMState, NET::Properties2());
|
|
|
|
|
info.setState(NET::Modal, NET::Modal);
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
}
|
|
|
|
|
QSignalSpy modalChangedSpy(dialog, &Window::modalChanged);
|
|
|
|
|
QVERIFY(modalChangedSpy.wait());
|
|
|
|
|
QVERIFY(dialog->isModal());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), dialog);
|
|
|
|
|
|
|
|
|
|
// Show another window.
|
|
|
|
|
Test::XcbConnectionPtr c1 = Test::createX11Connection();
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.get()));
|
|
|
|
|
X11Window *unrelated = createWindow(c1.get(), QRect(0, 0, 100, 200));
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
|
|
|
|
|
// Close the dialog.
|
|
|
|
|
QSignalSpy dialogClosedSpy(dialog, &Window::closed);
|
|
|
|
|
xcb_unmap_window(c.get(), dialog->window());
|
|
|
|
|
xcb_destroy_window(c.get(), dialog->window());
|
|
|
|
|
xcb_flush(c.get());
|
|
|
|
|
QVERIFY(dialogClosedSpy.wait());
|
|
|
|
|
QCOMPARE(workspace()->activeWindow(), unrelated);
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
WAYLANDTEST_MAIN(X11WindowTest)
|
|
|
|
|
#include "x11_window_test.moc"
|