Adapt to input region changes in kwayland-server

SurfaceInterface::inputIsInfinite() has been dropped. If the surface has
no any input region specified, SurfaceInterface::input() will return a
region that corresponds to the rect of the surface (0, 0, width, height).

While the new design is more robust, for example it's no longer possible
to forget to check SurfaceInterface::inputIsInfinite(), it has shown some
issues in the input stack of kwin.

Currently, acceptsInput() will return false if you attempt to click the
server-side decoration for a surface whose input region is not empty.

Therefore, it's possible for an application to set an input region with
a width and a height of 1. If user doesn't know about KSysGuard or the
possibility of closing apps via the task manager, they won't be able to
close such an application.

Another issue is that if an application has specified an empty input
region on purpose, user will be still able click it. With the new
behavior of SurfaceInterface::input(), this is no longer an issue and it
is handled properly by kwin.
This commit is contained in:
Vlad Zahorodnii 2020-10-17 15:47:25 +03:00
parent fea950f23a
commit 41d431de27
8 changed files with 76 additions and 18 deletions

View file

@ -2575,6 +2575,16 @@ QRect AbstractClient::inputGeometry() const
return Toplevel::inputGeometry();
}
bool AbstractClient::hitTest(const QPoint &point) const
{
if (isDecorated()) {
if (m_decoration.inputRegion.contains(mapToFrame(point))) {
return true;
}
}
return Toplevel::hitTest(point);
}
QRect AbstractClient::virtualKeyboardGeometry() const
{
return m_virtualKeyboardGeometry;

View file

@ -754,6 +754,7 @@ public:
virtual void showContextHelp();
QRect inputGeometry() const override;
bool hitTest(const QPoint &point) const override;
/**
* @returns the geometry of the virtual keyboard

View file

@ -25,6 +25,7 @@
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/compositor.h>
#include <KWayland/Client/pointer.h>
#include <KWayland/Client/region.h>
#include <KWayland/Client/seat.h>
#include <KWayland/Client/server_decoration.h>
#include <KWayland/Client/shm_pool.h>
@ -123,6 +124,8 @@ private Q_SLOTS:
void testResizeCursor();
void testMoveCursor();
void testHideShowCursor();
void testDefaultInputRegion();
void testEmptyInputRegion();
private:
void render(KWayland::Client::Surface *surface, const QSize &size = QSize(100, 50));
@ -1619,6 +1622,52 @@ void PointerInputTest::testHideShowCursor()
QCOMPARE(kwinApp()->platform()->isCursorHidden(), false);
}
void PointerInputTest::testDefaultInputRegion()
{
// This test verifies that a surface that hasn't specified the input region can be focused.
// Create a test client.
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QVERIFY(!shellSurface.isNull());
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(client);
// Move the point to the center of the surface.
Cursors::self()->mouse()->setPos(client->frameGeometry().center());
QCOMPARE(waylandServer()->seat()->focusedPointerSurface(), client->surface());
// Destroy the test client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(client));
}
void PointerInputTest::testEmptyInputRegion()
{
// This test verifies that a surface that has specified an empty input region can't be focused.
// Create a test client.
using namespace KWayland::Client;
QScopedPointer<Surface> surface(Test::createSurface());
QVERIFY(!surface.isNull());
std::unique_ptr<KWayland::Client::Region> inputRegion(m_compositor->createRegion(QRegion()));
surface->setInputRegion(inputRegion.get());
QScopedPointer<XdgShellSurface> shellSurface(Test::createXdgShellStableSurface(surface.data()));
QVERIFY(!shellSurface.isNull());
AbstractClient *client = Test::renderAndWaitForShown(surface.data(), QSize(100, 50), Qt::blue);
QVERIFY(client);
// Move the point to the center of the surface.
Cursors::self()->mouse()->setPos(client->frameGeometry().center());
QVERIFY(!waylandServer()->seat()->focusedPointerSurface());
// Destroy the test client.
shellSurface.reset();
QVERIFY(Test::waitForWindowDestroyed(client));
}
}
WAYLANDTEST_MAIN(KWin::PointerInputTest)

View file

@ -2418,17 +2418,6 @@ Qt::MouseButtons InputRedirection::qtButtonStates() const
return m_pointer->buttons();
}
static bool acceptsInput(Toplevel *t, const QPoint &pos)
{
const QRegion input = t->inputShape();
if (input.isEmpty()) {
return true;
}
// TODO: What about sub-surfaces sticking outside the main surface?
const QPoint localPoint = pos - t->bufferGeometry().topLeft();
return input.contains(localPoint);
}
Toplevel *InputRedirection::findToplevel(const QPoint &pos)
{
if (!Workspace::self()) {
@ -2443,7 +2432,7 @@ Toplevel *InputRedirection::findToplevel(const QPoint &pos)
}
const QList<Unmanaged *> &unmanaged = Workspace::self()->unmanagedList();
foreach (Unmanaged *u, unmanaged) {
if (u->inputGeometry().contains(pos) && acceptsInput(u, pos)) {
if (u->hitTest(pos)) {
return u;
}
}
@ -2482,7 +2471,7 @@ Toplevel *InputRedirection::findManagedToplevel(const QPoint &pos)
continue;
}
}
if (t->inputGeometry().contains(pos) && acceptsInput(t, pos)) {
if (t->hitTest(pos)) {
return t;
}
} while (it != stacking.begin());

View file

@ -107,9 +107,6 @@ NET::WindowType InputPanelV1Client::windowType(bool, int) const
QRect InputPanelV1Client::inputGeometry() const
{
if (surface()->inputIsInfinite()) {
return frameGeometry();
}
return surface()->input().boundingRect().translated(pos());
}

View file

@ -620,8 +620,7 @@ template <typename T>
static QRegion getConstraintRegion(Toplevel *t, T *constraint)
{
const QRegion windowShape = t->inputShape();
const QRegion windowRegion = windowShape.isEmpty() ? QRegion(0, 0, t->clientSize().width(), t->clientSize().height()) : windowShape;
const QRegion intersected = constraint->region().isEmpty() ? windowRegion : windowRegion.intersected(constraint->region());
const QRegion intersected = constraint->region().isEmpty() ? windowShape : windowShape.intersected(constraint->region());
return intersected.translated(t->pos() + t->clientPos());
}

View file

@ -789,6 +789,14 @@ QMatrix4x4 Toplevel::inputTransformation() const
return m;
}
bool Toplevel::hitTest(const QPoint &point) const
{
if (m_surface && m_surface->isMapped()) {
return m_surface->inputSurfaceAt(mapToLocal(point));
}
return inputGeometry().contains(point);
}
QPoint Toplevel::mapToFrame(const QPoint &point) const
{
return point - frameGeometry().topLeft();

View file

@ -540,6 +540,11 @@ public:
*/
virtual QMatrix4x4 inputTransformation() const;
/**
* Returns @c true if the toplevel can accept input at the specified position @a point.
*/
virtual bool hitTest(const QPoint &point) const;
/**
* The window has a popup grab. This means that when it got mapped the
* parent window had an implicit (pointer) grab.