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"
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include "composite.h"
|
|
|
|
|
#include "cursor.h"
|
2018-11-18 19:13:55 +00:00
|
|
|
|
#include "deleted.h"
|
2022-03-23 10:13:38 +00:00
|
|
|
|
#include "effectloader.h"
|
|
|
|
|
#include "effects.h"
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include "platform.h"
|
2017-07-23 09:50:58 +00:00
|
|
|
|
#include "screens.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>
|
|
|
|
|
|
2016-10-31 14:50:14 +00:00
|
|
|
|
#include <netwm.h>
|
|
|
|
|
#include <xcb/xcb_icccm.h>
|
|
|
|
|
|
|
|
|
|
using namespace KWin;
|
2017-07-23 09:50:58 +00:00
|
|
|
|
using namespace KWayland::Client;
|
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:
|
|
|
|
|
void initTestCase();
|
|
|
|
|
void init();
|
|
|
|
|
void cleanup();
|
|
|
|
|
|
2020-02-14 11:59:58 +00:00
|
|
|
|
void testMinimumSize();
|
|
|
|
|
void testMaximumSize();
|
|
|
|
|
void testResizeIncrements();
|
|
|
|
|
void testResizeIncrementsNoBaseSize();
|
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 09:50:58 +00:00
|
|
|
|
void testFullscreenLayerWithActiveWaylandWindow();
|
2017-07-23 14:18:09 +00:00
|
|
|
|
void testFocusInWithWaylandLastActiveWindow();
|
2017-07-26 05:08:27 +00:00
|
|
|
|
void testX11WindowId();
|
2017-08-13 14:52:14 +00:00
|
|
|
|
void testCaptionChanges();
|
2017-08-01 19:28:36 +00:00
|
|
|
|
void testCaptionWmName();
|
2017-09-18 16:37:45 +00:00
|
|
|
|
void testCaptionMultipleWindows();
|
2018-01-06 09:45:44 +00:00
|
|
|
|
void testFullscreenWindowGroups();
|
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();
|
2016-10-31 14:50:14 +00:00
|
|
|
|
};
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::initTestCase()
|
2016-10-31 14:50:14 +00:00
|
|
|
|
{
|
2022-03-23 10:13:38 +00:00
|
|
|
|
qRegisterMetaType<KWin::Deleted *>();
|
2022-04-22 17:39:12 +00:00
|
|
|
|
qRegisterMetaType<KWin::Window *>();
|
2020-07-07 09:32:29 +00:00
|
|
|
|
QSignalSpy applicationStartedSpy(kwinApp(), &Application::started);
|
|
|
|
|
QVERIFY(applicationStartedSpy.isValid());
|
2016-10-31 14:50:14 +00:00
|
|
|
|
kwinApp()->platform()->setInitialWindowSize(QSize(1280, 1024));
|
2020-12-09 13:06:15 +00:00
|
|
|
|
QVERIFY(waylandServer()->init(s_socketName));
|
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());
|
2021-05-08 00:08:22 +00:00
|
|
|
|
Test::initWaylandWorkspace();
|
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
|
|
|
|
{
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct XcbConnectionDeleter
|
|
|
|
|
{
|
|
|
|
|
static inline void cleanup(xcb_connection_t *pointer)
|
|
|
|
|
{
|
|
|
|
|
xcb_disconnect(pointer);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
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.
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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());
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.last().first().value<X11Window *>();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QVERIFY(client->isDecorated());
|
|
|
|
|
|
2022-04-22 17:39:12 +00:00
|
|
|
|
QSignalSpy clientStartMoveResizedSpy(client, &Window::clientStartUserMovedResized);
|
|
|
|
|
QSignalSpy clientStepUserMovedResizedSpy(client, &Window::clientStepUserMovedResized);
|
|
|
|
|
QSignalSpy clientFinishUserMovedResizedSpy(client, &Window::clientFinishUserMovedResized);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(client, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), client);
|
|
|
|
|
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
|
const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Left);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().width(), 100);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Right);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().width(), 100);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Right);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().width(), 108);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Up);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, -8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 200);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Down);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 200);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Down);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 208);
|
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
|
|
|
|
|
client->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
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.
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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());
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.last().first().value<X11Window *>();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QVERIFY(client->isDecorated());
|
|
|
|
|
|
2022-04-22 17:39:12 +00:00
|
|
|
|
QSignalSpy clientStartMoveResizedSpy(client, &Window::clientStartUserMovedResized);
|
|
|
|
|
QSignalSpy clientStepUserMovedResizedSpy(client, &Window::clientStepUserMovedResized);
|
|
|
|
|
QSignalSpy clientFinishUserMovedResizedSpy(client, &Window::clientFinishUserMovedResized);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(client, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), client);
|
|
|
|
|
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
|
const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Right);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().width(), 100);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Left);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(!clientStepUserMovedResizedSpy.wait(1000));
|
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 0);
|
|
|
|
|
QCOMPARE(client->clientSize().width(), 100);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Left);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().width(), 92);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Down);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 200);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Up);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(!frameGeometryChangedSpy.wait(1000));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 200);
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Up);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(-8, -8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize().height(), 192);
|
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
|
|
|
|
|
client->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testResizeIncrements()
|
2020-02-14 11:59:58 +00:00
|
|
|
|
{
|
|
|
|
|
// This test verifies that the resize increments constraint is correctly applied.
|
|
|
|
|
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_base_size(&hints, windowGeometry.width(), windowGeometry.height());
|
|
|
|
|
xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.last().first().value<X11Window *>();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QVERIFY(client->isDecorated());
|
|
|
|
|
|
2022-04-22 17:39:12 +00:00
|
|
|
|
QSignalSpy clientStartMoveResizedSpy(client, &Window::clientStartUserMovedResized);
|
|
|
|
|
QSignalSpy clientStepUserMovedResizedSpy(client, &Window::clientStepUserMovedResized);
|
|
|
|
|
QSignalSpy clientFinishUserMovedResizedSpy(client, &Window::clientFinishUserMovedResized);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(client, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), client);
|
|
|
|
|
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
|
const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Right);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize(), QSize(106, 200));
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Down);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize(), QSize(106, 205));
|
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
|
|
|
|
|
client->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
c.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testResizeIncrementsNoBaseSize()
|
2020-02-14 11:59:58 +00:00
|
|
|
|
{
|
|
|
|
|
// Create an xcb window.
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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());
|
|
|
|
|
xcb_icccm_size_hints_set_resize_inc(&hints, 3, 5);
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.last().first().value<X11Window *>();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QVERIFY(client->isDecorated());
|
|
|
|
|
|
2022-04-22 17:39:12 +00:00
|
|
|
|
QSignalSpy clientStartMoveResizedSpy(client, &Window::clientStartUserMovedResized);
|
|
|
|
|
QSignalSpy clientStepUserMovedResizedSpy(client, &Window::clientStepUserMovedResized);
|
|
|
|
|
QSignalSpy clientFinishUserMovedResizedSpy(client, &Window::clientFinishUserMovedResized);
|
|
|
|
|
QSignalSpy frameGeometryChangedSpy(client, &Window::frameGeometryChanged);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Begin resize.
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
workspace()->slotWindowResize();
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), client);
|
|
|
|
|
QCOMPARE(clientStartMoveResizedSpy.count(), 1);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
2020-04-02 16:18:01 +00:00
|
|
|
|
const QPoint cursorPos = KWin::Cursors::self()->mouse()->pos();
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Right);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 0));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 1);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize(), QSize(106, 200));
|
|
|
|
|
|
|
|
|
|
client->keyPressEvent(Qt::Key_Down);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
client->updateInteractiveMoveResize(KWin::Cursors::self()->mouse()->pos());
|
2020-04-02 16:18:01 +00:00
|
|
|
|
QCOMPARE(KWin::Cursors::self()->mouse()->pos(), cursorPos + QPoint(8, 8));
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(clientStepUserMovedResizedSpy.count(), 2);
|
2021-12-24 09:20:00 +00:00
|
|
|
|
QVERIFY(frameGeometryChangedSpy.wait());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QCOMPARE(client->clientSize(), QSize(106, 205));
|
|
|
|
|
|
|
|
|
|
// Finish the resize operation.
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 0);
|
|
|
|
|
client->keyPressEvent(Qt::Key_Enter);
|
|
|
|
|
QCOMPARE(clientFinishUserMovedResizedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(workspace()->moveResizeClient(), nullptr);
|
2021-04-30 18:06:58 +00:00
|
|
|
|
QVERIFY(!client->isInteractiveResize());
|
2020-02-14 11:59:58 +00:00
|
|
|
|
|
|
|
|
|
// Destroy the window.
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2020-02-14 11:59:58 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
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
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
NETWinInfo winInfo(c.data(), w, 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);
|
2016-10-31 14:50:14 +00:00
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
// we should get a client for it
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2016-10-31 14:50:14 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QCOMPARE(client->window(), w);
|
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);
|
|
|
|
|
QCOMPARE(client->caption(), QString::fromUtf8(expectedTitle));
|
2016-10-31 14:50:14 +00:00
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2016-10-31 14:50:14 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
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
|
|
|
|
|
QCOMPARE(screens()->count(), 1);
|
|
|
|
|
|
|
|
|
|
// first create an X11 window
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
// we should get a client for it
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-07-23 09:50:58 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QCOMPARE(client->window(), w);
|
|
|
|
|
QVERIFY(!client->isFullScreen());
|
|
|
|
|
QVERIFY(client->isActive());
|
|
|
|
|
QCOMPARE(client->layer(), NormalLayer);
|
|
|
|
|
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
|
|
|
|
QVERIFY(client->isFullScreen());
|
|
|
|
|
QCOMPARE(client->layer(), ActiveLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
|
|
|
|
|
|
|
|
|
// now let's open a Wayland window
|
2021-09-03 17:54:03 +00:00
|
|
|
|
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
2021-05-11 05:26:51 +00:00
|
|
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
2017-07-23 09:50:58 +00:00
|
|
|
|
auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
|
|
|
|
|
QVERIFY(waylandClient);
|
|
|
|
|
QVERIFY(waylandClient->isActive());
|
|
|
|
|
QCOMPARE(waylandClient->layer(), NormalLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
QCOMPARE(client->layer(), NormalLayer);
|
|
|
|
|
|
|
|
|
|
// now activate fullscreen again
|
|
|
|
|
workspace()->activateClient(client);
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
QCOMPARE(client->layer(), ActiveLayer);
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
|
|
|
|
workspace()->activateClient(waylandClient);
|
|
|
|
|
QTRY_VERIFY(waylandClient->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2017-07-23 09:50:58 +00:00
|
|
|
|
|
2017-07-23 18:02:57 +00:00
|
|
|
|
// back to x window
|
|
|
|
|
workspace()->activateClient(client);
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
// remove fullscreen
|
|
|
|
|
QVERIFY(client->isFullScreen());
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
|
|
|
|
QVERIFY(!client->isFullScreen());
|
|
|
|
|
// and fullscreen again
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
|
|
|
|
QVERIFY(client->isFullScreen());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
|
|
|
|
workspace()->activateClient(waylandClient);
|
|
|
|
|
QTRY_VERIFY(waylandClient->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// back to X11 window
|
|
|
|
|
workspace()->activateClient(client);
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
// remove fullscreen
|
|
|
|
|
QVERIFY(client->isFullScreen());
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
|
|
|
|
QVERIFY(!client->isFullScreen());
|
|
|
|
|
// and fullscreen through X API
|
|
|
|
|
NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
|
|
|
|
info.setState(NET::FullScreen, NET::FullScreen);
|
|
|
|
|
NETRootInfo rootInfo(c.data(), NET::Properties());
|
|
|
|
|
rootInfo.setActiveWindow(w, NET::FromApplication, XCB_CURRENT_TIME, XCB_WINDOW_NONE);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QTRY_VERIFY(client->isFullScreen());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), client);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
|
|
|
|
|
// activate wayland window again
|
|
|
|
|
workspace()->activateClient(waylandClient);
|
|
|
|
|
QTRY_VERIFY(waylandClient->isActive());
|
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2021-05-12 18:07:08 +00:00
|
|
|
|
QCOMPARE(workspace()->stackingOrder().last(), waylandClient);
|
2017-07-23 18:02:57 +00:00
|
|
|
|
QCOMPARE(client->layer(), NormalLayer);
|
|
|
|
|
|
2017-07-23 09:50:58 +00:00
|
|
|
|
// close the window
|
|
|
|
|
shellSurface.reset();
|
|
|
|
|
surface.reset();
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(waylandClient));
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
QCOMPARE(client->layer(), ActiveLayer);
|
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testFocusInWithWaylandLastActiveWindow()
|
2017-07-23 14:18:09 +00:00
|
|
|
|
{
|
|
|
|
|
// this test verifies that Workspace::allowClientActivation does not crash if last client was a Wayland client
|
|
|
|
|
|
|
|
|
|
// create an X11 window
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
// we should get a client for it
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-07-23 14:18:09 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QCOMPARE(client->window(), w);
|
|
|
|
|
QVERIFY(client->isActive());
|
|
|
|
|
|
|
|
|
|
// create Wayland window
|
2021-09-03 17:54:03 +00:00
|
|
|
|
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
2021-05-11 05:26:51 +00:00
|
|
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
2017-07-23 14:18:09 +00:00
|
|
|
|
auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
|
|
|
|
|
QVERIFY(waylandClient);
|
|
|
|
|
QVERIFY(waylandClient->isActive());
|
|
|
|
|
// activate no window
|
|
|
|
|
workspace()->setActiveClient(nullptr);
|
|
|
|
|
QVERIFY(!waylandClient->isActive());
|
|
|
|
|
QVERIFY(!workspace()->activeClient());
|
|
|
|
|
// and close Wayland window again
|
|
|
|
|
shellSurface.reset();
|
|
|
|
|
surface.reset();
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(waylandClient));
|
|
|
|
|
|
|
|
|
|
// and try to activate the x11 client through X11 api
|
|
|
|
|
const auto cookie = xcb_set_input_focus_checked(c.data(), XCB_INPUT_FOCUS_NONE, w, XCB_CURRENT_TIME);
|
|
|
|
|
auto error = xcb_request_check(c.data(), cookie);
|
|
|
|
|
QVERIFY(!error);
|
|
|
|
|
// this accesses last_active_client on trying to activate
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testX11WindowId()
|
2017-07-26 05:08:27 +00:00
|
|
|
|
{
|
|
|
|
|
// create an X11 window
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
// we should get a client for it
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-07-26 05:08:27 +00:00
|
|
|
|
QVERIFY(client);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client->window(), w);
|
2017-07-26 05:08:27 +00:00
|
|
|
|
QVERIFY(client->isActive());
|
|
|
|
|
QCOMPARE(client->window(), w);
|
2018-11-18 19:13:55 +00:00
|
|
|
|
QCOMPARE(client->internalId().isNull(), false);
|
|
|
|
|
const auto uuid = client->internalId();
|
|
|
|
|
QUuid deletedUuid;
|
|
|
|
|
QCOMPARE(deletedUuid.isNull(), true);
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
connect(client, &X11Window::windowClosed, this, [&deletedUuid](Window *, Deleted *d) {
|
2022-03-23 10:13:38 +00:00
|
|
|
|
deletedUuid = d->internalId();
|
|
|
|
|
});
|
2017-07-26 05:08:27 +00:00
|
|
|
|
|
|
|
|
|
NETRootInfo rootInfo(c.data(), NET::WMAllProperties);
|
|
|
|
|
QCOMPARE(rootInfo.activeWindow(), client->window());
|
|
|
|
|
|
|
|
|
|
// activate a wayland window
|
2021-09-03 17:54:03 +00:00
|
|
|
|
QScopedPointer<KWayland::Client::Surface> surface(Test::createSurface());
|
2021-05-11 05:26:51 +00:00
|
|
|
|
QScopedPointer<Test::XdgToplevel> shellSurface(Test::createXdgToplevelSurface(surface.data()));
|
2017-07-26 05:08:27 +00:00
|
|
|
|
auto waylandClient = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
|
|
|
|
|
QVERIFY(waylandClient);
|
|
|
|
|
QVERIFY(waylandClient->isActive());
|
|
|
|
|
xcb_flush(kwinApp()->x11Connection());
|
|
|
|
|
|
|
|
|
|
NETRootInfo rootInfo2(c.data(), NET::WMAllProperties);
|
|
|
|
|
QCOMPARE(rootInfo2.activeWindow(), 0u);
|
|
|
|
|
|
|
|
|
|
// back to X11 client
|
|
|
|
|
shellSurface.reset();
|
|
|
|
|
surface.reset();
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(waylandClient));
|
|
|
|
|
|
|
|
|
|
QTRY_VERIFY(client->isActive());
|
|
|
|
|
NETRootInfo rootInfo3(c.data(), NET::WMAllProperties);
|
|
|
|
|
QCOMPARE(rootInfo3.activeWindow(), client->window());
|
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2018-11-18 19:13:55 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
|
|
|
|
|
QCOMPARE(deletedUuid.isNull(), false);
|
|
|
|
|
QCOMPARE(deletedUuid, uuid);
|
2017-07-26 05:08:27 +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
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
|
|
|
|
info.setName("foo");
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
// we should get a client for it
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(client);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client->window(), w);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QCOMPARE(client->caption(), QStringLiteral("foo"));
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy captionChangedSpy(client, &X11Window::captionChanged);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(captionChangedSpy.isValid());
|
|
|
|
|
info.setName("bar");
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QVERIFY(captionChangedSpy.wait());
|
|
|
|
|
QCOMPARE(client->caption(), QStringLiteral("bar"));
|
|
|
|
|
|
|
|
|
|
// and destroy the window again
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy windowClosedSpy(client, &X11Window::windowClosed);
|
2017-08-13 14:52:14 +00:00
|
|
|
|
QVERIFY(windowClosedSpy.isValid());
|
|
|
|
|
xcb_unmap_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QVERIFY(windowClosedSpy.wait());
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
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
|
|
|
|
|
QSignalSpy clientAddedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(clientAddedSpy.isValid());
|
|
|
|
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
|
|
|
QVERIFY(clientAddedSpy.wait());
|
|
|
|
|
QCOMPARE(clientAddedSpy.count(), 1);
|
|
|
|
|
QCOMPARE(workspace()->clientList().count(), 1);
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *glxgearsClient = workspace()->clientList().first();
|
2017-08-01 19:28:36 +00:00
|
|
|
|
QCOMPARE(glxgearsClient->caption(), QStringLiteral("glxgears"));
|
|
|
|
|
|
|
|
|
|
glxgears.terminate();
|
|
|
|
|
QVERIFY(glxgears.waitForFinished());
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
void X11WindowTest::testCaptionMultipleWindows()
|
2017-09-18 16:37:45 +00:00
|
|
|
|
{
|
|
|
|
|
// BUG 384760
|
|
|
|
|
// create first window
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
NETWinInfo info(c.data(), w, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
|
|
|
|
info.setName("foo");
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-09-18 16:37:45 +00:00
|
|
|
|
QVERIFY(client);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client->window(), w);
|
2017-09-18 16:37:45 +00:00
|
|
|
|
QCOMPARE(client->caption(), QStringLiteral("foo"));
|
|
|
|
|
|
|
|
|
|
// create second window with same caption
|
|
|
|
|
xcb_window_t w2 = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints);
|
|
|
|
|
NETWinInfo info2(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
|
|
|
|
info2.setName("foo");
|
|
|
|
|
info2.setIconName("foo");
|
|
|
|
|
xcb_map_window(c.data(), w2);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
windowCreatedSpy.clear();
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client2 = windowCreatedSpy.first().first().value<X11Window *>();
|
2017-09-18 16:37:45 +00:00
|
|
|
|
QVERIFY(client2);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client2->window(), w2);
|
2017-09-18 16:37:45 +00:00
|
|
|
|
QCOMPARE(client2->caption(), QStringLiteral("foo <2>\u200E"));
|
|
|
|
|
NETWinInfo info3(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
|
|
|
|
|
QCOMPARE(QByteArray(info3.visibleName()), QByteArrayLiteral("foo <2>\u200E"));
|
|
|
|
|
QCOMPARE(QByteArray(info3.visibleIconName()), QByteArrayLiteral("foo <2>\u200E"));
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
QSignalSpy captionChangedSpy(client2, &X11Window::captionChanged);
|
2017-09-18 16:37:45 +00:00
|
|
|
|
QVERIFY(captionChangedSpy.isValid());
|
|
|
|
|
|
|
|
|
|
NETWinInfo info4(c.data(), w2, kwinApp()->x11RootWindow(), NET::Properties(), NET::Properties2());
|
|
|
|
|
info4.setName("foobar");
|
|
|
|
|
info4.setIconName("foobar");
|
|
|
|
|
xcb_map_window(c.data(), w2);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QVERIFY(captionChangedSpy.wait());
|
|
|
|
|
QCOMPARE(client2->caption(), QStringLiteral("foobar"));
|
|
|
|
|
NETWinInfo info5(kwinApp()->x11Connection(), w2, kwinApp()->x11RootWindow(), NET::WMVisibleName | NET::WMVisibleIconName, NET::Properties2());
|
|
|
|
|
QCOMPARE(QByteArray(info5.visibleName()), QByteArray());
|
|
|
|
|
QTRY_COMPARE(QByteArray(info5.visibleIconName()), QByteArray());
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2018-01-06 09:45:44 +00:00
|
|
|
|
QVERIFY(client);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client->window(), w);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
QCOMPARE(client->isActive(), true);
|
|
|
|
|
|
|
|
|
|
QCOMPARE(client->isFullScreen(), false);
|
|
|
|
|
QCOMPARE(client->layer(), NormalLayer);
|
|
|
|
|
workspace()->slotWindowFullScreen();
|
|
|
|
|
QCOMPARE(client->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(client->layer(), ActiveLayer);
|
|
|
|
|
|
|
|
|
|
// now let's create a second window
|
|
|
|
|
windowCreatedSpy.clear();
|
|
|
|
|
xcb_window_t w2 = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w2, rootWindow(),
|
|
|
|
|
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());
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(c.data(), w2, &hints2);
|
|
|
|
|
xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w2, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
|
|
|
|
|
xcb_map_window(c.data(), w2);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client2 = windowCreatedSpy.first().first().value<X11Window *>();
|
2018-01-06 09:45:44 +00:00
|
|
|
|
QVERIFY(client2);
|
|
|
|
|
QVERIFY(client != client2);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client2->window(), w2);
|
2018-01-06 09:45:44 +00:00
|
|
|
|
QCOMPARE(client2->isActive(), true);
|
|
|
|
|
QCOMPARE(client2->group(), client->group());
|
|
|
|
|
// first client should be moved back to normal layer
|
|
|
|
|
QCOMPARE(client->isActive(), false);
|
|
|
|
|
QCOMPARE(client->isFullScreen(), true);
|
|
|
|
|
QCOMPARE(client->layer(), NormalLayer);
|
2018-01-06 10:24:31 +00:00
|
|
|
|
|
|
|
|
|
// activating the fullscreen window again, should move it to active layer
|
|
|
|
|
workspace()->activateClient(client);
|
|
|
|
|
QTRY_COMPARE(client->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.
|
|
|
|
|
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> connection(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(connection.data()));
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
const xcb_window_t window1 = xcb_generate_id(connection.data());
|
|
|
|
|
xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window1, rootWindow(),
|
|
|
|
|
windowGeometry.x(), windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(), windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(connection.data(), window1, &hints);
|
|
|
|
|
xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window1,
|
|
|
|
|
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window1);
|
|
|
|
|
xcb_map_window(connection.data(), window1);
|
|
|
|
|
xcb_flush(connection.data());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client1 = windowCreatedSpy.first().first().value<X11Window *>();
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QVERIFY(client1);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client1->window(), window1);
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QCOMPARE(client1->isActive(), true);
|
|
|
|
|
|
|
|
|
|
// Create the second test window.
|
|
|
|
|
const xcb_window_t window2 = xcb_generate_id(connection.data());
|
|
|
|
|
xcb_create_window(connection.data(), XCB_COPY_FROM_PARENT, window2, rootWindow(),
|
|
|
|
|
windowGeometry.x(), windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(), windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 0, nullptr);
|
|
|
|
|
xcb_icccm_set_wm_normal_hints(connection.data(), window2, &hints);
|
|
|
|
|
xcb_change_property(connection.data(), XCB_PROP_MODE_REPLACE, window2,
|
|
|
|
|
atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &window2);
|
|
|
|
|
xcb_map_window(connection.data(), window2);
|
|
|
|
|
xcb_flush(connection.data());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client2 = windowCreatedSpy.last().first().value<X11Window *>();
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QVERIFY(client2);
|
2020-11-04 15:49:10 +00:00
|
|
|
|
QCOMPARE(client2->window(), window2);
|
2020-06-16 08:03:25 +00:00
|
|
|
|
QCOMPARE(client2->isActive(), true);
|
|
|
|
|
|
|
|
|
|
// When the second test window is destroyed, the window manager will attempt to activate the
|
|
|
|
|
// next client in the focus chain, which is the first window.
|
|
|
|
|
xcb_set_input_focus(connection.data(), XCB_INPUT_FOCUS_POINTER_ROOT, window1, XCB_CURRENT_TIME);
|
|
|
|
|
xcb_destroy_window(connection.data(), window2);
|
|
|
|
|
xcb_flush(connection.data());
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(client2));
|
|
|
|
|
QVERIFY(client1->isActive());
|
|
|
|
|
|
|
|
|
|
// Destroy the first test window.
|
|
|
|
|
xcb_destroy_window(connection.data(), window1);
|
|
|
|
|
xcb_flush(connection.data());
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(client1));
|
|
|
|
|
}
|
|
|
|
|
|
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.
|
|
|
|
|
QScopedPointer<xcb_connection_t, XcbConnectionDeleter> c(xcb_connect(nullptr, nullptr));
|
|
|
|
|
QVERIFY(!xcb_connection_has_error(c.data()));
|
|
|
|
|
const QRect windowGeometry(0, 0, 100, 200);
|
|
|
|
|
xcb_window_t w = xcb_generate_id(c.data());
|
|
|
|
|
xcb_create_window(c.data(), XCB_COPY_FROM_PARENT, w, rootWindow(),
|
|
|
|
|
windowGeometry.x(),
|
|
|
|
|
windowGeometry.y(),
|
|
|
|
|
windowGeometry.width(),
|
|
|
|
|
windowGeometry.height(),
|
|
|
|
|
0, XCB_WINDOW_CLASS_INPUT_OUTPUT, XCB_COPY_FROM_PARENT, 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_set_wm_normal_hints(c.data(), w, &hints);
|
|
|
|
|
xcb_change_property(c.data(), XCB_PROP_MODE_REPLACE, w, atoms->wm_client_leader, XCB_ATOM_WINDOW, 32, 1, &w);
|
|
|
|
|
xcb_map_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
|
|
|
|
|
QSignalSpy windowCreatedSpy(workspace(), &Workspace::clientAdded);
|
|
|
|
|
QVERIFY(windowCreatedSpy.isValid());
|
|
|
|
|
QVERIFY(windowCreatedSpy.wait());
|
2022-04-22 17:54:31 +00:00
|
|
|
|
X11Window *client = windowCreatedSpy.first().first().value<X11Window *>();
|
2020-07-20 19:33:19 +00:00
|
|
|
|
QVERIFY(client);
|
|
|
|
|
QCOMPARE(client->pos(), QPoint(0, 0));
|
|
|
|
|
|
|
|
|
|
// Let's pretend that there is a script that really wants the client to be at (100, 100).
|
2022-04-22 17:39:12 +00:00
|
|
|
|
connect(client, &Window::frameGeometryChanged, this, [client]() {
|
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
|
|
|
|
client->moveResize(QRect(QPoint(100, 100), client->size()));
|
2020-07-20 19:33:19 +00:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Trigger the lambda above.
|
|
|
|
|
client->move(QPoint(40, 50));
|
|
|
|
|
|
|
|
|
|
// Eventually, the client will end up at (100, 100).
|
|
|
|
|
QCOMPARE(client->pos(), QPoint(100, 100));
|
|
|
|
|
|
|
|
|
|
// Destroy the test window.
|
|
|
|
|
xcb_destroy_window(c.data(), w);
|
|
|
|
|
xcb_flush(c.data());
|
|
|
|
|
QVERIFY(Test::waitForWindowDestroyed(client));
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-22 17:54:31 +00:00
|
|
|
|
WAYLANDTEST_MAIN(X11WindowTest)
|
|
|
|
|
#include "x11_window_test.moc"
|