Client: Don't leak wl_data_offers

[ChangeLog][QPA plugin] Fixed a leak of wl_data_offers.

Data offers would previously leak when a surface with keyboard focus was
destroyed... or if it was hidden... or if it changed type, and so on.

Make keyboard focus follow a wl_surface instead of a QWaylandWindow.

This also fixes a couple of other minor issues, such as the mRepeatTimer not
stopping when a wl_surface was destroyed.

Ideally, we would have a QWaylandSurface class separate from the
QWaylandWindow, but that's out of scope for this fix.

Fixes: QTBUG-73825
Change-Id: I56e502512c3959e3fcdb63744adc4a7698e3d53d
Reviewed-by: Paul Olav Tvete <paul.tvete@qt.io>
This commit is contained in:
Johan Klokkhammer Helsing 2019-02-14 13:41:30 +01:00 committed by Johan Helsing
parent 48696dd85e
commit 04be9cf380
5 changed files with 62 additions and 50 deletions

View File

@ -50,6 +50,7 @@
#endif
#if QT_CONFIG(wayland_datadevice)
#include "qwaylanddatadevicemanager_p.h"
#include "qwaylanddatadevice_p.h"
#endif
#if QT_CONFIG(cursor)
#include <wayland-cursor.h>

View File

@ -168,9 +168,9 @@ QWaylandInputDevice::Keyboard::~Keyboard()
wl_keyboard_destroy(object());
}
void QWaylandInputDevice::Keyboard::stopRepeat()
QWaylandWindow *QWaylandInputDevice::Keyboard::focusWindow() const
{
mRepeatTimer.stop();
return mFocus ? QWaylandWindow::fromWlSurface(mFocus) : nullptr;
}
QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat)
@ -421,12 +421,6 @@ QWaylandInputDevice::Touch *QWaylandInputDevice::createTouch(QWaylandInputDevice
return new Touch(device);
}
void QWaylandInputDevice::handleWindowDestroyed(QWaylandWindow *window)
{
if (mKeyboard && window == mKeyboard->mFocus)
mKeyboard->stopRepeat();
}
void QWaylandInputDevice::handleEndDrag()
{
if (mTouch)
@ -470,7 +464,7 @@ QWaylandWindow *QWaylandInputDevice::pointerFocus() const
QWaylandWindow *QWaylandInputDevice::keyboardFocus() const
{
return mKeyboard ? mKeyboard->mFocus : nullptr;
return mKeyboard ? mKeyboard->focusWindow() : nullptr;
}
QWaylandWindow *QWaylandInputDevice::touchFocus() const
@ -767,12 +761,19 @@ void QWaylandInputDevice::Keyboard::keyboard_enter(uint32_t time, struct wl_surf
Q_UNUSED(time);
Q_UNUSED(keys);
if (!surface)
if (!surface) {
// Ignoring wl_keyboard.enter event with null surface. This is either a compositor bug,
// or it's a race with a wl_surface.destroy request. In either case, ignore the event.
return;
}
if (mFocus) {
qCWarning(lcQpaWayland()) << "Unexpected wl_keyboard.enter event. Keyboard already has focus";
disconnect(focusWindow(), &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
}
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
mFocus = window;
mFocus = surface;
connect(focusWindow(), &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
mParent->mQDisplay->handleKeyboardFocusChanged(mParent);
}
@ -780,18 +781,20 @@ void QWaylandInputDevice::Keyboard::keyboard_enter(uint32_t time, struct wl_surf
void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t time, struct wl_surface *surface)
{
Q_UNUSED(time);
Q_UNUSED(surface);
if (surface) {
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
window->unfocus();
if (!surface) {
// Either a compositor bug, or a race condition with wl_surface.destroy, ignore the event.
return;
}
mFocus = nullptr;
mParent->mQDisplay->handleKeyboardFocusChanged(mParent);
mRepeatTimer.stop();
if (surface != mFocus) {
qCWarning(lcQpaWayland) << "Ignoring unexpected wl_keyboard.leave event."
<< "wl_surface argument does not match the current focus"
<< "This is most likely a compositor bug";
return;
}
disconnect(focusWindow(), &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
handleFocusLost();
}
static void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers,
@ -816,7 +819,7 @@ static void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Q
void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, uint32_t key, uint32_t state)
{
QWaylandWindow *window = mFocus;
auto *window = focusWindow();
if (!window) {
// We destroyed the keyboard focus surface, but the server didn't get the message yet...
// or the server didn't send an enter event first. In either case, ignore the event.
@ -896,14 +899,15 @@ void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time,
void QWaylandInputDevice::Keyboard::repeatKey()
{
if (!mFocus) {
auto *window = focusWindow();
if (!window) {
// We destroyed the keyboard focus surface, but the server didn't get the message yet...
// or the server didn't send an enter event first.
return;
}
mRepeatTimer.setInterval(mRepeatRate);
sendKey(mFocus->window(), mRepeatTime, QEvent::KeyRelease, mRepeatKey, modifiers(), mRepeatCode,
sendKey(window->window(), mRepeatTime, QEvent::KeyRelease, mRepeatKey, modifiers(), mRepeatCode,
#if QT_CONFIG(xkbcommon)
mRepeatSym, mNativeModifiers,
#else
@ -911,7 +915,7 @@ void QWaylandInputDevice::Keyboard::repeatKey()
#endif
mRepeatText, true);
sendKey(mFocus->window(), mRepeatTime, QEvent::KeyPress, mRepeatKey, modifiers(), mRepeatCode,
sendKey(window->window(), mRepeatTime, QEvent::KeyPress, mRepeatKey, modifiers(), mRepeatCode,
#if QT_CONFIG(xkbcommon)
mRepeatSym, mNativeModifiers,
#else
@ -920,6 +924,27 @@ void QWaylandInputDevice::Keyboard::repeatKey()
mRepeatText, true);
}
void QWaylandInputDevice::Keyboard::handleFocusDestroyed()
{
// The signal is emitted by QWaylandWindow, which is not necessarily destroyed along with the
// surface, so we still need to disconnect the signal
auto *window = qobject_cast<QWaylandWindow *>(sender());
disconnect(window, &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
Q_ASSERT(window->object() == mFocus);
handleFocusLost();
}
void QWaylandInputDevice::Keyboard::handleFocusLost()
{
mFocus = nullptr;
#if QT_CONFIG(clipboard)
if (auto *dataDevice = mParent->dataDevice())
dataDevice->invalidateSelectionOffer();
#endif
mParent->mQDisplay->handleKeyboardFocusChanged(mParent);
mRepeatTimer.stop();
}
void QWaylandInputDevice::Keyboard::keyboard_modifiers(uint32_t serial,
uint32_t mods_depressed,
uint32_t mods_latched,
@ -1021,7 +1046,7 @@ void QWaylandInputDevice::handleTouchPoint(int id, double x, double y, Qt::Touch
if (!win && mPointer)
win = mPointer->mFocus;
if (!win && mKeyboard)
win = mKeyboard->mFocus;
win = mKeyboard->focusWindow();
if (!win || !win->window())
return;

View File

@ -113,7 +113,6 @@ public:
#if QT_CONFIG(cursor)
void setCursor(const QCursor *cursor, const QSharedPointer<QWaylandBuffer> &cachedBuffer = {}, int fallbackOutputScale = 1);
#endif
void handleWindowDestroyed(QWaylandWindow *window);
void handleEndDrag();
#if QT_CONFIG(wayland_datadevice)
@ -193,7 +192,7 @@ public:
Keyboard(QWaylandInputDevice *p);
~Keyboard() override;
void stopRepeat();
QWaylandWindow *focusWindow() const;
void keyboard_keymap(uint32_t format,
int32_t fd,
@ -213,7 +212,7 @@ public:
void keyboard_repeat_info(int32_t rate, int32_t delay) override;
QWaylandInputDevice *mParent = nullptr;
QPointer<QWaylandWindow> mFocus;
::wl_surface *mFocus = nullptr;
#if QT_CONFIG(xkbcommon)
xkb_context *mXkbContext = nullptr;
xkb_keymap *mXkbMap = nullptr;
@ -238,6 +237,8 @@ public:
private slots:
void repeatKey();
void handleFocusDestroyed();
void handleFocusLost();
private:
#if QT_CONFIG(xkbcommon)

View File

@ -51,11 +51,6 @@
#include "qwaylanddecorationfactory_p.h"
#include "qwaylandshmbackingstore_p.h"
#if QT_CONFIG(wayland_datadevice)
#include "qwaylanddatadevice_p.h"
#endif
#include <QtCore/QFileInfo>
#include <QtCore/QPointer>
#include <QtCore/QRegularExpression>
@ -95,10 +90,6 @@ QWaylandWindow::~QWaylandWindow()
if (isInitialized())
reset(false);
QList<QWaylandInputDevice *> inputDevices = mDisplay->inputDevices();
for (int i = 0; i < inputDevices.size(); ++i)
inputDevices.at(i)->handleWindowDestroyed(this);
const QWindow *parent = window();
foreach (QWindow *w, QGuiApplication::topLevelWindows()) {
if (w->transientParent() == parent)
@ -236,8 +227,10 @@ void QWaylandWindow::reset(bool sendDestroyEvent)
mShellSurface = nullptr;
delete mSubSurfaceWindow;
mSubSurfaceWindow = nullptr;
if (isInitialized())
if (isInitialized()) {
emit wlSurfaceDestroyed();
destroy();
}
mScreens.clear();
if (mFrameCallback) {
@ -972,16 +965,6 @@ void QWaylandWindow::requestActivateWindow()
qCWarning(lcQpaWayland) << "Wayland does not support QWindow::requestActivate()";
}
void QWaylandWindow::unfocus()
{
#if QT_CONFIG(clipboard)
QWaylandInputDevice *inputDevice = mDisplay->currentInputDevice();
if (inputDevice && inputDevice->dataDevice()) {
inputDevice->dataDevice()->invalidateSelectionOffer();
}
#endif
}
bool QWaylandWindow::isExposed() const
{
if (mShellSurface)

View File

@ -153,7 +153,6 @@ public:
void requestActivateWindow() override;
bool isExposed() const override;
bool isActive() const override;
void unfocus();
QWaylandAbstractDecoration *decoration() const;
@ -200,6 +199,9 @@ public:
public slots:
void applyConfigure();
signals:
void wlSurfaceDestroyed();
protected:
void surface_enter(struct ::wl_output *output) override;
void surface_leave(struct ::wl_output *output) override;