Clean up the focus handling for text input.

Usually, a client will only use text input v2/v3. Do not return the focused
surface for text input if it has no relevant text input resource.
If text-input object is created after surface get the focus, send
enter to this text input object. Ensure sendEnter and sendLeave always
appear in pair.

Also, use the same technique in text-input-v2 for text-input-v3 to
handle per resource's enable/disable state, and only send update to
enabled text-input-v3 object.
This commit is contained in:
Weng Xuetian 2021-12-19 00:00:22 -08:00 committed by Xuetian Weng
parent f2bf7fb1f8
commit afc2a1c0aa
6 changed files with 178 additions and 51 deletions

View file

@ -33,6 +33,7 @@ private Q_SLOTS:
void testEnterLeave_data();
void testEnterLeave();
void testFocusedBeforeCreateTextInput();
void testShowHidePanel();
void testCursorRectangle();
void testPreferredLanguage();
@ -267,6 +268,50 @@ void TextInputTest::testEnterLeave()
QVERIFY(!textInput->enteredSurface());
}
void TextInputTest::testFocusedBeforeCreateTextInput()
{
// this test verifies that enter leave are sent correctly
QScopedPointer<Surface> surface(m_compositor->createSurface());
auto serverSurface = waitForSurface();
// now let's try to enter it
QSignalSpy textInputChangedSpy(m_seatInterface, &SeatInterface::focusedTextInputSurfaceChanged);
QVERIFY(textInputChangedSpy.isValid());
QVERIFY(!m_seatInterface->focusedTextInputSurface());
m_seatInterface->setFocusedKeyboardSurface(serverSurface);
QCOMPARE(m_seatInterface->focusedTextInputSurface(), serverSurface);
QCOMPARE(textInputChangedSpy.count(), 1);
// This is null because there is no text input object for this client.
QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
QVERIFY(serverSurface);
QScopedPointer<TextInput> textInput(createTextInput());
QVERIFY(!textInput.isNull());
QSignalSpy enteredSpy(textInput.data(), &TextInput::entered);
QVERIFY(enteredSpy.isValid());
QSignalSpy leftSpy(textInput.data(), &TextInput::left);
QVERIFY(leftSpy.isValid());
// and trigger an enter
if (enteredSpy.isEmpty()) {
QVERIFY(enteredSpy.wait());
}
QCOMPARE(enteredSpy.count(), 1);
QCOMPARE(textInput->enteredSurface(), surface.data());
// This is not null anymore because there is a text input object associated with it.
QCOMPARE(m_seatInterface->textInputV2()->surface(), serverSurface);
// now trigger a leave
m_seatInterface->setFocusedKeyboardSurface(nullptr);
QCOMPARE(textInputChangedSpy.count(), 2);
QVERIFY(leftSpy.wait());
QVERIFY(!textInput->enteredSurface());
QCOMPARE(m_seatInterface->textInputV2()->surface(), nullptr);
QCOMPARE(m_seatInterface->focusedTextInputSurface(), nullptr);
}
void TextInputTest::testShowHidePanel()
{
// this test verifies that the requests for show/hide panel work

View file

@ -553,6 +553,7 @@ void TestTextInputV3Interface::testMultipleTextinputs()
m_serverTextInputV3 = m_seat->textInputV3();
QVERIFY(m_serverTextInputV3);
QVERIFY(!m_serverTextInputV3->isEnabled());
QSignalSpy committedSpy(m_serverTextInputV3, &TextInputV3Interface::stateCommitted);
// Enable ti1
@ -560,6 +561,7 @@ void TestTextInputV3Interface::testMultipleTextinputs()
ti1->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
QVERIFY(m_serverTextInputV3->isEnabled());
// Send another three commits on ti1
ti1->enable();
@ -567,12 +569,14 @@ void TestTextInputV3Interface::testMultipleTextinputs()
ti1->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
QVERIFY(m_serverTextInputV3->isEnabled());
ti1->enable();
ti1->set_content_type(QtWayland::zwp_text_input_v3::content_hint_none, QtWayland::zwp_text_input_v3::content_purpose_normal);
ti1->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 3);
QVERIFY(m_serverTextInputV3->isEnabled());
// at this point total commit count to ti1 is 3
QSignalSpy doneSpy1(ti1, &TextInputV3::done);
@ -590,12 +594,14 @@ void TestTextInputV3Interface::testMultipleTextinputs()
ti1->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 4);
QVERIFY(!m_serverTextInputV3->isEnabled());
// first commit to ti2
ti2->enable();
ti2->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 1);
QVERIFY(m_serverTextInputV3->isEnabled());
// send commit string
m_serverTextInputV3->commitString("Hello world");
@ -608,6 +614,7 @@ void TestTextInputV3Interface::testMultipleTextinputs()
ti2->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 2);
QVERIFY(!m_serverTextInputV3->isEnabled());
// now re-enable the ti1 and verify sending commits to t2 hasn't affected it's serial
// Enable ti1 : 5 commits now
@ -615,6 +622,7 @@ void TestTextInputV3Interface::testMultipleTextinputs()
ti1->commit();
QVERIFY(committedSpy.wait());
QCOMPARE(committedSpy.last().at(0).value<quint32>(), 5);
QVERIFY(m_serverTextInputV3->isEnabled());
// send done signal
m_serverTextInputV3->commitString("Hello");

View file

@ -1184,26 +1184,25 @@ void SeatInterface::setFocusedTextInputSurface(SurfaceInterface *surface)
{
const quint32 serial = d->display->nextSerial();
if (d->focusedTextInputSurface == surface) {
return;
}
if (d->focusedTextInputSurface) {
disconnect(d->focusedSurfaceDestroyConnection);
}
if (d->focusedTextInputSurface != surface) {
d->textInputV2->d->sendLeave(serial, d->focusedTextInputSurface);
d->textInputV3->d->sendLeave(d->focusedTextInputSurface);
d->focusedTextInputSurface = surface;
Q_EMIT focusedTextInputSurfaceChanged();
}
d->focusedTextInputSurface = surface;
Q_EMIT focusedTextInputSurfaceChanged();
if (d->focusedTextInputSurface) {
if (surface) {
d->focusedSurfaceDestroyConnection = connect(surface, &SurfaceInterface::aboutToBeDestroyed, this, [this] {
setFocusedTextInputSurface(nullptr);
});
d->textInputV2->d->sendEnter(surface, serial);
d->textInputV3->d->sendEnter(surface);
}
d->textInputV2->d->sendEnter(surface, serial);
d->textInputV3->d->sendEnter(surface);
// TODO: setFocusedSurface like in other interfaces
}
SurfaceInterface *SeatInterface::focusedTextInputSurface() const

View file

@ -127,7 +127,12 @@ void TextInputManagerV2InterfacePrivate::zwp_text_input_manager_v2_get_text_inpu
}
TextInputV2InterfacePrivate *textInputPrivate = TextInputV2InterfacePrivate::get(s->textInputV2());
textInputPrivate->add(resource->client(), id, resource->version());
auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version());
// Send enter to this new text input object if the surface is already focused.
const quint32 serial = s->display()->nextSerial();
if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) {
textInputPrivate->send_enter(textInputResource->handle, serial, textInputPrivate->surface->resource());
}
}
TextInputManagerV2Interface::TextInputManagerV2Interface(Display *display, QObject *parent)
@ -141,25 +146,19 @@ TextInputManagerV2Interface::~TextInputManagerV2Interface() = default;
void TextInputV2InterfacePrivate::sendEnter(SurfaceInterface *newSurface, quint32 serial)
{
EnabledEmitter emitter(q);
if (surface) {
sendLeave(serial, newSurface);
}
// It should be always synchronized with SeatInterface::focusedTextInputSurface.
Q_ASSERT(!surface && newSurface);
surface = newSurface;
if (surface) {
const auto clientResources = textInputsForClient(newSurface->client());
for (auto resource : clientResources) {
send_enter(resource->handle, serial, newSurface->resource());
}
const auto clientResources = textInputsForClient(newSurface->client());
for (auto resource : clientResources) {
send_enter(resource->handle, serial, newSurface->resource());
}
}
void TextInputV2InterfacePrivate::sendLeave(quint32 serial, SurfaceInterface *leavingSurface)
{
if (leavingSurface != surface || !leavingSurface) {
return;
}
// It should be always synchronized with SeatInterface::focusedTextInputSurface.
Q_ASSERT(leavingSurface && surface == leavingSurface);
EnabledEmitter emitter(q);
surface.clear();
const auto clientResources = textInputsForClient(leavingSurface->client());
@ -335,7 +334,14 @@ void TextInputV2InterfacePrivate::zwp_text_input_v2_enable(Resource *resource, w
Q_UNUSED(resource)
EnabledEmitter emitter(q);
auto enabledSurface = SurfaceInterface::get(s);
if (m_enabledSurfaces.contains(enabledSurface)) {
return;
}
m_enabledSurfaces.insert(enabledSurface);
QObject::connect(enabledSurface, &SurfaceInterface::aboutToBeDestroyed, q, [this, enabledSurface] {
EnabledEmitter emitter(q);
m_enabledSurfaces.remove(enabledSurface);
});
}
void TextInputV2InterfacePrivate::zwp_text_input_v2_disable(Resource *resource, wl_resource *s)
@ -343,8 +349,8 @@ void TextInputV2InterfacePrivate::zwp_text_input_v2_disable(Resource *resource,
Q_UNUSED(resource)
EnabledEmitter emitter(q);
auto disabledSurface = SurfaceInterface::get(s);
QObject::disconnect(disabledSurface, &SurfaceInterface::aboutToBeDestroyed, q, nullptr);
m_enabledSurfaces.remove(disabledSurface);
if (disabledSurface == surface) {
q->setInputPanelState(false, {0, 0, 0, 0});
}
@ -529,6 +535,14 @@ void TextInputV2Interface::setModifiersMap(const QByteArray &modifiersMap)
QPointer<SurfaceInterface> TextInputV2Interface::surface() const
{
if (!d->surface) {
return nullptr;
}
if (!d->resourceMap().contains(d->surface->client()->client())) {
return nullptr;
}
return d->surface;
}

View file

@ -108,6 +108,26 @@ TextInputManagerV3InterfacePrivate::TextInputManagerV3InterfacePrivate(TextInput
{
}
class EnabledEmitter
{
public:
EnabledEmitter(TextInputV3Interface *q)
: q(q)
, m_wasEnabled(q->isEnabled())
{
}
~EnabledEmitter()
{
if (m_wasEnabled != q->isEnabled()) {
Q_EMIT q->enabledChanged();
}
}
private:
TextInputV3Interface *q;
const bool m_wasEnabled;
};
void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_destroy(Resource *resource)
{
wl_resource_destroy(resource->handle);
@ -121,7 +141,11 @@ void TextInputManagerV3InterfacePrivate::zwp_text_input_manager_v3_get_text_inpu
return;
}
TextInputV3InterfacePrivate *textInputPrivate = TextInputV3InterfacePrivate::get(s->textInputV3());
textInputPrivate->add(resource->client(), id, resource->version());
auto *textInputResource = textInputPrivate->add(resource->client(), id, resource->version());
// Send enter to this new text input object if the surface is already focused.
if (textInputPrivate->surface && textInputPrivate->surface->client()->client() == resource->client()) {
textInputPrivate->send_enter(textInputResource->handle, textInputPrivate->surface->resource());
}
}
TextInputManagerV3Interface::TextInputManagerV3Interface(Display *display, QObject *parent)
@ -142,35 +166,37 @@ void TextInputV3InterfacePrivate::zwp_text_input_v3_bind_resource(Resource *reso
{
// we initialize the serial for the resource to be 0
serialHash.insert(resource, 0);
enabled.insert(resource, false);
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_destroy(Resource *resource)
{
// drop resource from the serial hash
serialHash.remove(resource);
enabled.remove(resource);
}
void TextInputV3InterfacePrivate::sendEnter(SurfaceInterface *s)
void TextInputV3InterfacePrivate::sendEnter(SurfaceInterface *newSurface)
{
if (!s) {
return;
}
surface = QPointer<SurfaceInterface>(s);
const auto clientResources = textInputsForClient(s->client());
EnabledEmitter emitter(q);
// It should be always synchronized with SeatInterface::focusedTextInputSurface.
Q_ASSERT(!surface && newSurface);
surface = newSurface;
const auto clientResources = textInputsForClient(newSurface->client());
for (auto resource : clientResources) {
send_enter(resource->handle, s->resource());
send_enter(resource->handle, newSurface->resource());
}
}
void TextInputV3InterfacePrivate::sendLeave(SurfaceInterface *s)
void TextInputV3InterfacePrivate::sendLeave(SurfaceInterface *leavingSurface)
{
if (!s) {
return;
}
EnabledEmitter emitter(q);
// It should be always synchronized with SeatInterface::focusedTextInputSurface.
Q_ASSERT(leavingSurface && surface == leavingSurface);
surface.clear();
const auto clientResources = textInputsForClient(s->client());
const auto clientResources = textInputsForClient(leavingSurface->client());
for (auto resource : clientResources) {
send_leave(resource->handle, s->resource());
send_leave(resource->handle, leavingSurface->resource());
}
}
@ -179,7 +205,7 @@ void TextInputV3InterfacePrivate::sendPreEdit(const QString &text, const quint32
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
for (auto resource : textInputs) {
send_preedit_string(resource->handle, text, cursorBegin, cursorEnd);
}
@ -190,7 +216,7 @@ void TextInputV3InterfacePrivate::commitString(const QString &text)
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
for (auto resource : textInputs) {
send_commit_string(resource->handle, text);
}
@ -201,7 +227,7 @@ void TextInputV3InterfacePrivate::deleteSurroundingText(quint32 before, quint32
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
for (auto resource : textInputs) {
send_delete_surrounding_text(resource->handle, before, after);
}
@ -212,7 +238,8 @@ void TextInputV3InterfacePrivate::done()
if (!surface) {
return;
}
const QList<Resource *> textInputs = textInputsForClient(surface->client());
const QList<Resource *> textInputs = enabledTextInputsForClient(surface->client());
for (auto resource : textInputs) {
// zwp_text_input_v3.done takes the serial argument which is equal to number of commit requests issued
send_done(resource->handle, serialHash[resource]);
@ -224,6 +251,29 @@ QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::text
return resourceMap().values(client->client());
}
QList<TextInputV3InterfacePrivate::Resource *> TextInputV3InterfacePrivate::enabledTextInputsForClient(ClientConnection *client) const
{
QList<TextInputV3InterfacePrivate::Resource *> result;
const auto [start, end] = resourceMap().equal_range(client->client());
for (auto it = start; it != end; ++it) {
if (enabled[*it]) {
result.append(*it);
}
}
return result;
}
bool TextInputV3InterfacePrivate::isEnabled() const
{
if (!surface) {
return false;
}
const auto clientResources = textInputsForClient(surface->client());
return std::any_of(clientResources.begin(), clientResources.end(), [this](Resource *resource) {
return enabled[resource];
});
}
void TextInputV3InterfacePrivate::zwp_text_input_v3_enable(Resource *resource)
{
// reset pending state to default
@ -284,11 +334,12 @@ void TextInputV3InterfacePrivate::zwp_text_input_v3_set_text_change_cause(Resour
void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource)
{
EnabledEmitter emitter(q);
serialHash[resource]++;
if (enabled != pending.enabled) {
enabled = pending.enabled;
Q_EMIT q->enabledChanged();
auto &resourceEnabled = enabled[resource];
if (resourceEnabled != pending.enabled) {
resourceEnabled = pending.enabled;
}
if (surroundingTextChangeCause != pending.surroundingTextChangeCause) {
@ -299,14 +350,14 @@ void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource)
if (contentHints != pending.contentHints || contentPurpose != pending.contentPurpose) {
contentHints = pending.contentHints;
contentPurpose = pending.contentPurpose;
if (enabled) {
if (resourceEnabled) {
Q_EMIT q->contentTypeChanged();
}
}
if (cursorRectangle != pending.cursorRectangle) {
cursorRectangle = pending.cursorRectangle;
if (enabled) {
if (resourceEnabled) {
Q_EMIT q->cursorRectangleChanged(cursorRectangle);
}
}
@ -316,7 +367,7 @@ void TextInputV3InterfacePrivate::zwp_text_input_v3_commit(Resource *resource)
surroundingText = pending.surroundingText;
surroundingTextCursorPosition = pending.surroundingTextCursorPosition;
surroundingTextSelectionAnchor = pending.surroundingTextSelectionAnchor;
if (enabled) {
if (resourceEnabled) {
Q_EMIT q->surroundingTextChanged();
}
}
@ -391,6 +442,14 @@ void TextInputV3Interface::done()
QPointer<SurfaceInterface> TextInputV3Interface::surface() const
{
if (!d->surface) {
return nullptr;
}
if (!d->resourceMap().contains(d->surface->client()->client())) {
return nullptr;
}
return d->surface;
}
@ -401,7 +460,7 @@ QRect TextInputV3Interface::cursorRectangle() const
bool TextInputV3Interface::isEnabled() const
{
return d->enabled;
return d->isEnabled();
}
}

View file

@ -42,7 +42,9 @@ public:
void deleteSurroundingText(quint32 beforeLength, quint32 afterLength);
void done();
bool isEnabled() const;
QList<TextInputV3InterfacePrivate::Resource *> textInputsForClient(ClientConnection *client) const;
QList<TextInputV3InterfacePrivate::Resource *> enabledTextInputsForClient(ClientConnection *client) const;
static TextInputV3InterfacePrivate *get(TextInputV3Interface *inputInterface)
{
@ -55,7 +57,6 @@ public:
SeatInterface *seat = nullptr;
QPointer<SurfaceInterface> surface;
bool enabled = false;
QString surroundingText;
qint32 surroundingTextCursorPosition = 0;
@ -74,6 +75,7 @@ public:
} pending;
QHash<Resource *, quint32> serialHash;
QHash<Resource *, bool> enabled;
void defaultPending();