Commit 55f3a295 authored by Martin Flöser's avatar Martin Flöser
Browse files

[server] Pass keyboard focus to child surface on pointer click

If the focused pointer and keyboard surface is the same we use pointer
clicks as a hint to which child surface should have keyboard focus.

Keyboard focus handling for sub surfaces is rather limited overall.
We just don't have a good model on how to determine which child surface
should get the keyboard focus. When passing focus to a surface there
is no way to know which of the sub-surfaces should get the focus.
Ideally the client should handle this, but that's just not the case.

The best we have is a reference through the pointer. But that's of
course also limited. Keyboard focus passed to the surface for another
reason (Alt+Tab) cannot select the proper sub-surface without interaction
from another input device.

Reviewers: #plasma

Subscribers: plasma-devel

Projects: #plasma

Differential Revision: https://phabricator.kde.org/D1330
parent 518ef359
......@@ -67,6 +67,7 @@ private Q_SLOTS:
void testPointerButton_data();
void testPointerButton();
void testPointerSubSurfaceTree();
void testKeyboardSubSurfaceTreeFromPointer();
void testCursor();
void testCursorDamage();
void testKeyboard();
......@@ -766,6 +767,108 @@ void TestWaylandSeat::testPointerSubSurfaceTree()
QCOMPARE(pointer->enteredSurface(), parentSurface.data());
}
void TestWaylandSeat::testKeyboardSubSurfaceTreeFromPointer()
{
// this test verifies that when clicking on a sub-surface the keyboard focus passes to it
using namespace KWayland::Client;
using namespace KWayland::Server;
// first create the pointer
QSignalSpy hasPointerChangedSpy(m_seat, &Seat::hasPointerChanged);
QVERIFY(hasPointerChangedSpy.isValid());
m_seatInterface->setHasPointer(true);
QVERIFY(hasPointerChangedSpy.wait());
QScopedPointer<Pointer> pointer(m_seat->createPointer());
// and create keyboard
QSignalSpy hasKeyboardChangedSpy(m_seat, &Seat::hasKeyboardChanged);
QVERIFY(hasKeyboardChangedSpy.isValid());
m_seatInterface->setHasKeyboard(true);
QVERIFY(hasKeyboardChangedSpy.wait());
QScopedPointer<Keyboard> keyboard(m_seat->createKeyboard());
// create a sub surface tree
// parent surface (100, 100) with one sub surface taking the half of it's size (50, 100)
// which has two further children (50, 50) which are overlapping
QSignalSpy surfaceCreatedSpy(m_compositorInterface, &CompositorInterface::surfaceCreated);
QVERIFY(surfaceCreatedSpy.isValid());
QScopedPointer<Surface> parentSurface(m_compositor->createSurface());
QScopedPointer<Surface> childSurface(m_compositor->createSurface());
QScopedPointer<Surface> grandChild1Surface(m_compositor->createSurface());
QScopedPointer<Surface> grandChild2Surface(m_compositor->createSurface());
QScopedPointer<SubSurface> childSubSurface(m_subCompositor->createSubSurface(childSurface.data(), parentSurface.data()));
QScopedPointer<SubSurface> grandChild1SubSurface(m_subCompositor->createSubSurface(grandChild1Surface.data(), childSurface.data()));
QScopedPointer<SubSurface> grandChild2SubSurface(m_subCompositor->createSubSurface(grandChild2Surface.data(), childSurface.data()));
grandChild2SubSurface->setPosition(QPoint(0, 25));
// let's map the surfaces
auto render = [this] (Surface *s, const QSize &size) {
QImage image(size, QImage::Format_ARGB32);
image.fill(Qt::black);
s->attachBuffer(m_shm->createBuffer(image));
s->damage(QRect(QPoint(0, 0), size));
s->commit(Surface::CommitFlag::None);
};
render(grandChild2Surface.data(), QSize(50, 50));
render(grandChild1Surface.data(), QSize(50, 50));
render(childSurface.data(), QSize(50, 100));
render(parentSurface.data(), QSize(100, 100));
QVERIFY(surfaceCreatedSpy.wait());
auto serverSurface = surfaceCreatedSpy.first().first().value<SurfaceInterface*>();
QVERIFY(serverSurface->isMapped());
// pass keyboard focus to the main surface
QSignalSpy enterSpy(keyboard.data(), &Keyboard::entered);
QVERIFY(enterSpy.isValid());
QSignalSpy leftSpy(keyboard.data(), &Keyboard::left);
QVERIFY(leftSpy.isValid());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QVERIFY(enterSpy.wait());
QCOMPARE(enterSpy.count(), 1);
QCOMPARE(leftSpy.count(), 0);
QCOMPARE(keyboard->enteredSurface(), parentSurface.data());
// now pass also pointer focus to the surface
QSignalSpy pointerEnterSpy(pointer.data(), &Pointer::entered);
QVERIFY(pointerEnterSpy.isValid());
quint32 timestamp = 1;
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->setPointerPos(QPointF(25, 50));
m_seatInterface->setFocusedPointerSurface(serverSurface);
QVERIFY(pointerEnterSpy.wait());
QCOMPARE(pointerEnterSpy.count(), 1);
// should not have affected the keyboard
QCOMPARE(enterSpy.count(), 1);
QCOMPARE(leftSpy.count(), 0);
// let's click
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->pointerButtonPressed(Qt::LeftButton);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->pointerButtonReleased(Qt::LeftButton);
QVERIFY(enterSpy.wait());
QCOMPARE(enterSpy.count(), 2);
QCOMPARE(leftSpy.count(), 1);
QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data());
// click on same surface should not trigger another enter
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->pointerButtonPressed(Qt::LeftButton);
m_seatInterface->setTimestamp(timestamp++);
m_seatInterface->pointerButtonReleased(Qt::LeftButton);
QVERIFY(!enterSpy.wait(200));
QCOMPARE(enterSpy.count(), 2);
QCOMPARE(leftSpy.count(), 1);
QCOMPARE(keyboard->enteredSurface(), grandChild2Surface.data());
// unfocus keyboard
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QVERIFY(leftSpy.wait());
QCOMPARE(enterSpy.count(), 2);
QCOMPARE(leftSpy.count(), 2);
}
void TestWaylandSeat::testCursor()
{
using namespace KWayland::Client;
......
......@@ -42,6 +42,38 @@ KeyboardInterface::Private::Private(SeatInterface *s, wl_resource *parentResourc
{
}
void KeyboardInterface::Private::focusChildSurface(const QPointer<SurfaceInterface> &childSurface, quint32 serial)
{
if (focusedChildSurface == childSurface) {
return;
}
sendLeave(focusedChildSurface.data(), serial);
focusedChildSurface = childSurface;
sendEnter(focusedChildSurface.data(), serial);
}
void KeyboardInterface::Private::sendLeave(SurfaceInterface *surface, quint32 serial)
{
if (surface && resource && surface->resource()) {
wl_keyboard_send_leave(resource, serial, surface->resource());
}
}
void KeyboardInterface::Private::sendEnter(SurfaceInterface *surface, quint32 serial)
{
wl_array keys;
wl_array_init(&keys);
const auto states = seat->pressedKeys();
for (auto it = states.begin(); it != states.end(); ++it) {
uint32_t *k = reinterpret_cast<uint32_t*>(wl_array_add(&keys, sizeof(uint32_t)));
*k = *it;
}
wl_keyboard_send_enter(resource, serial, surface->resource(), &keys);
wl_array_release(&keys);
sendModifiers();
}
#ifndef DOXYGEN_SHOULD_SKIP_THIS
const struct wl_keyboard_interface KeyboardInterface::Private::s_interface {
releaseCallback
......@@ -99,12 +131,9 @@ void KeyboardInterface::Private::sendModifiers()
void KeyboardInterface::setFocusedSurface(SurfaceInterface *surface, quint32 serial)
{
Q_D();
if (d->focusedSurface) {
if (d->resource && d->focusedSurface->resource()) {
wl_keyboard_send_leave(d->resource, serial, d->focusedSurface->resource());
}
disconnect(d->destroyConnection);
}
d->sendLeave(d->focusedChildSurface, serial);
disconnect(d->destroyConnection);
d->focusedChildSurface.clear();
d->focusedSurface = surface;
if (!d->focusedSurface) {
return;
......@@ -113,20 +142,12 @@ void KeyboardInterface::setFocusedSurface(SurfaceInterface *surface, quint32 ser
[this] {
Q_D();
d->focusedSurface = nullptr;
d->focusedChildSurface.clear();
}
);
d->focusedChildSurface = QPointer<SurfaceInterface>(surface);
wl_array keys;
wl_array_init(&keys);
const auto states = d->seat->pressedKeys();
for (auto it = states.begin(); it != states.end(); ++it) {
uint32_t *k = reinterpret_cast<uint32_t*>(wl_array_add(&keys, sizeof(uint32_t)));
*k = *it;
}
wl_keyboard_send_enter(d->resource, serial, d->focusedSurface->resource(), &keys);
wl_array_release(&keys);
d->sendModifiers();
d->sendEnter(d->focusedSurface, serial);
d->client->flush();
}
......
......@@ -22,6 +22,8 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "keyboard_interface.h"
#include "resource_p.h"
#include <QPointer>
namespace KWayland
{
namespace Server
......@@ -36,8 +38,13 @@ public:
void sendModifiers();
void sendModifiers(quint32 depressed, quint32 latched, quint32 locked, quint32 group, quint32 serial);
void focusChildSurface(const QPointer<SurfaceInterface> &childSurface, quint32 serial);
void sendLeave(SurfaceInterface *surface, quint32 serial);
void sendEnter(SurfaceInterface *surface, quint32 serial);
SeatInterface *seat;
SurfaceInterface *focusedSurface = nullptr;
QPointer<SurfaceInterface> focusedChildSurface;
QMetaObject::Connection destroyConnection;
private:
......
......@@ -23,7 +23,9 @@ License along with this library. If not, see <http://www.gnu.org/licenses/>.
#include "datadevice_interface.h"
#include "datasource_interface.h"
#include "keyboard_interface.h"
#include "keyboard_interface_p.h"
#include "pointer_interface.h"
#include "pointer_interface_p.h"
#include "surface_interface.h"
// Wayland
#ifndef WL_SEAT_NAME_SINCE_VERSION
......@@ -714,6 +716,10 @@ void SeatInterface::pointerButtonPressed(quint32 button)
}
if (d->globalPointer.focus.pointer && d->globalPointer.focus.surface) {
d->globalPointer.focus.pointer->buttonPressed(button, serial);
if (d->globalPointer.focus.surface == d->keys.focus.surface && d->keys.focus.keyboard) {
// update the focused child surface
d->keys.focus.keyboard->d_func()->focusChildSurface(d->globalPointer.focus.pointer->d_func()->focusedChildSurface, serial);
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment