Merge remote-tracking branch 'qt/5.13' into dev

Change-Id: I3dc204fcaa71c01a80b0c622443012eb07964431
This commit is contained in:
Paul Olav Tvete 2019-03-21 11:42:15 +01:00
commit dfd4633804
27 changed files with 958 additions and 337 deletions

View File

@ -1,5 +1,6 @@
{
"module": "waylandclient",
"condition": "module.gui",
"depends": [
"gui-private"
],

View File

@ -380,7 +380,14 @@ void QWaylandXdgSurfaceV6::setGrabPopup(QWaylandWindow *parent, QWaylandInputDev
auto *top = m_shell->m_topmostGrabbingPopup;
if (top && top->m_xdgSurface != parentXdgSurface) {
qCWarning(lcQpaWayland) << "setGrabPopup called for a surface that was not the topmost popup, positions might be off.";
qCWarning(lcQpaWayland) << "setGrabPopup called with a parent," << parentXdgSurface
<< "which does not match the current topmost grabbing popup,"
<< top->m_xdgSurface << "According to the xdg-shell-v6 protocol, this"
<< "is not allowed. The wayland QPA plugin is currently handling"
<< "it by setting the parent to the topmost grabbing popup."
<< "Note, however, that this may cause positioning errors and"
<< "popups closing unxpectedly because xdg-shell-v6 mandate that child"
<< "popups close before parents";
parent = top->m_xdgSurface->m_window;
}
setPopup(parent);

View File

@ -414,7 +414,14 @@ void QWaylandXdgSurface::setGrabPopup(QWaylandWindow *parent, QWaylandInputDevic
auto *top = m_shell->m_topmostGrabbingPopup;
if (top && top->m_xdgSurface != parentXdgSurface) {
qCWarning(lcQpaWayland) << "setGrabPopup called for a surface that was not the topmost popup, positions might be off.";
qCWarning(lcQpaWayland) << "setGrabPopup called with a parent," << parentXdgSurface
<< "which does not match the current topmost grabbing popup,"
<< top->m_xdgSurface << "According to the xdg-shell protocol, this"
<< "is not allowed. The wayland QPA plugin is currently handling"
<< "it by setting the parent to the topmost grabbing popup."
<< "Note, however, that this may cause positioning errors and"
<< "popups closing unxpectedly because xdg-shell mandate that child"
<< "popups close before parents";
parent = top->m_xdgSurface->m_window;
}
setPopup(parent);

View File

@ -100,14 +100,19 @@ void QWaylandAbstractDecoration::setWaylandWindow(QWaylandWindow *window)
d->m_wayland_window = window;
}
// \a size is without margins
// Creates regions like this on the outside of a rectangle with inner size \a size
// -----
// | |
// -----
// I.e. the top and bottom extends into the corners
static QRegion marginsRegion(const QSize &size, const QMargins &margins)
{
QRegion r;
r += QRect(0, 0, size.width(), margins.top()); // top
r += QRect(0, size.height()+margins.top(), size.width(), margins.bottom()); //bottom
r += QRect(0, 0, margins.left(), size.height()); //left
r += QRect(size.width()+margins.left(), 0, margins.right(), size.height()); // right
const int widthWithMargins = margins.left() + size.width() + margins.right();
r += QRect(0, 0, widthWithMargins, margins.top()); // top
r += QRect(0, size.height()+margins.top(), widthWithMargins, margins.bottom()); //bottom
r += QRect(0, margins.top(), margins.left(), size.height()); //left
r += QRect(size.width()+margins.left(), margins.top(), margins.right(), size.height()); // right
return r;
}

View File

@ -41,7 +41,6 @@
#include "qwaylanddisplay_p.h"
#include "qwaylandinputdevice_p.h"
#include "qwaylandscreen_p.h"
#include "qwaylandshmbackingstore_p.h"
#include <QtGui/QImageReader>
@ -53,12 +52,6 @@ QT_BEGIN_NAMESPACE
namespace QtWaylandClient {
QWaylandCursorTheme *QWaylandCursorTheme::create(QWaylandShm *shm, int size)
{
static QString themeName = qEnvironmentVariable("XCURSOR_THEME", QStringLiteral("default"));
return create(shm, size, themeName);
}
QWaylandCursorTheme *QWaylandCursorTheme::create(QWaylandShm *shm, int size, const QString &themeName)
{
QByteArray nameBytes = themeName.toLocal8Bit();
@ -244,56 +237,32 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
return image;
}
QWaylandCursor::QWaylandCursor(QWaylandScreen *screen)
: mDisplay(screen->display())
, mCursorTheme(mDisplay->loadCursorTheme(screen->devicePixelRatio()))
QWaylandCursor::QWaylandCursor(QWaylandDisplay *display)
: mDisplay(display)
{
}
QSharedPointer<QWaylandBuffer> QWaylandCursor::cursorBitmapImage(const QCursor *cursor)
QSharedPointer<QWaylandBuffer> QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor)
{
if (cursor->shape() != Qt::BitmapCursor)
return QSharedPointer<QWaylandShmBuffer>();
Q_ASSERT(cursor->shape() == Qt::BitmapCursor);
const QImage &img = cursor->pixmap().toImage();
QSharedPointer<QWaylandShmBuffer> buffer(new QWaylandShmBuffer(mDisplay, img.size(), img.format()));
memcpy(buffer->image()->bits(), img.bits(), img.sizeInBytes());
return buffer;
}
struct wl_cursor_image *QWaylandCursor::cursorImage(Qt::CursorShape shape)
{
if (!mCursorTheme)
return nullptr;
return mCursorTheme->cursorImage(shape);
QSharedPointer<QWaylandShmBuffer> buffer(new QWaylandShmBuffer(display, img.size(), img.format()));
memcpy(buffer->image()->bits(), img.bits(), size_t(img.sizeInBytes()));
return std::move(buffer);
}
void QWaylandCursor::changeCursor(QCursor *cursor, QWindow *window)
{
const Qt::CursorShape newShape = cursor ? cursor->shape() : Qt::ArrowCursor;
Q_UNUSED(window);
// Create the buffer here so we don't have to create one per input device
QSharedPointer<QWaylandBuffer> bitmapBuffer;
if (cursor && cursor->shape() == Qt::BitmapCursor)
bitmapBuffer = cursorBitmapBuffer(mDisplay, cursor);
if (newShape == Qt::BlankCursor) {
mDisplay->setCursor(nullptr, nullptr, 1);
return;
}
if (newShape == Qt::BitmapCursor) {
mDisplay->setCursor(cursorBitmapImage(cursor), cursor->hotSpot(), window->screen()->devicePixelRatio());
return;
}
if (!mCursorTheme) {
qCWarning(lcQpaWayland) << "Can't set cursor from shape with no cursor theme";
return;
}
if (struct ::wl_cursor_image *image = mCursorTheme->cursorImage(newShape)) {
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
mDisplay->setCursor(buffer, image, window->screen()->devicePixelRatio());
return;
}
qCWarning(lcQpaWayland) << "Unable to change to cursor" << cursor;
int fallbackOutputScale = int(window->devicePixelRatio());
const auto seats = mDisplay->inputDevices();
for (auto *seat : seats)
seat->setCursor(cursor, bitmapBuffer, fallbackOutputScale);
}
void QWaylandCursor::pointerEvent(const QMouseEvent &event)
@ -312,6 +281,6 @@ void QWaylandCursor::setPos(const QPoint &pos)
qCWarning(lcQpaWayland) << "Setting cursor position is not possible on wayland";
}
}
} // namespace QtWaylandClient
QT_END_NAMESPACE

View File

@ -73,7 +73,6 @@ class QWaylandShm;
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
{
public:
static QWaylandCursorTheme *create(QWaylandShm *shm, int size);
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
~QWaylandCursorTheme();
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
@ -122,19 +121,18 @@ private:
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursor : public QPlatformCursor
{
public:
QWaylandCursor(QWaylandScreen *screen);
explicit QWaylandCursor(QWaylandDisplay *display);
void changeCursor(QCursor *cursor, QWindow *window) override;
void pointerEvent(const QMouseEvent &event) override;
QPoint pos() const override;
void setPos(const QPoint &pos) override;
QSharedPointer<QWaylandBuffer> cursorBitmapImage(const QCursor *cursor);
static QSharedPointer<QWaylandBuffer> cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor);
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
private:
QWaylandDisplay *mDisplay = nullptr;
QWaylandCursorTheme *mCursorTheme = nullptr;
QPoint mLastPos;
};

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>
@ -160,7 +161,7 @@ QWaylandDisplay::~QWaylandDisplay(void)
delete mDndSelectionHandler.take();
#endif
#if QT_CONFIG(cursor)
qDeleteAll(mCursorThemesBySize);
qDeleteAll(mCursorThemes);
#endif
if (mDisplay)
wl_display_disconnect(mDisplay);
@ -505,40 +506,20 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const
#if QT_CONFIG(cursor)
void QWaylandDisplay::setCursor(struct wl_buffer *buffer, struct wl_cursor_image *image, qreal dpr)
QWaylandCursor *QWaylandDisplay::waylandCursor()
{
/* Qt doesn't tell us which input device we should set the cursor
* for, so set it for all devices. */
for (int i = 0; i < mInputDevices.count(); i++) {
QWaylandInputDevice *inputDevice = mInputDevices.at(i);
inputDevice->setCursor(buffer, image, dpr);
}
if (!mCursor)
mCursor.reset(new QWaylandCursor(this));
return mCursor.data();
}
void QWaylandDisplay::setCursor(const QSharedPointer<QWaylandBuffer> &buffer, const QPoint &hotSpot, qreal dpr)
QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(const QString &name, int pixelSize)
{
/* Qt doesn't tell us which input device we should set the cursor
* for, so set it for all devices. */
for (int i = 0; i < mInputDevices.count(); i++) {
QWaylandInputDevice *inputDevice = mInputDevices.at(i);
inputDevice->setCursor(buffer, hotSpot, dpr);
}
}
QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(qreal devicePixelRatio)
{
constexpr int defaultCursorSize = 32;
static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
int cursorSize = xCursorSize > 0 ? xCursorSize : defaultCursorSize;
if (compositorVersion() >= 3) // set_buffer_scale is not supported on earlier versions
cursorSize *= devicePixelRatio;
if (auto *theme = mCursorThemesBySize.value(cursorSize, nullptr))
if (auto *theme = mCursorThemes.value({name, pixelSize}, nullptr))
return theme;
if (auto *theme = QWaylandCursorTheme::create(shm(), cursorSize)) {
mCursorThemesBySize[cursorSize] = theme;
if (auto *theme = QWaylandCursorTheme::create(shm(), pixelSize, name)) {
mCursorThemes[{name, pixelSize}] = theme;
return theme;
}
@ -547,6 +528,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(qreal devicePixelRatio)
#endif // QT_CONFIG(cursor)
}
} // namespace QtWaylandClient
QT_END_NAMESPACE

View File

@ -93,6 +93,7 @@ class QWaylandWindow;
class QWaylandIntegration;
class QWaylandHardwareIntegration;
class QWaylandShellSurface;
class QWaylandCursor;
class QWaylandCursorTheme;
typedef void (*RegistryListener)(void *data,
@ -121,9 +122,8 @@ public:
QWaylandWindowManagerIntegration *windowManagerIntegration() const;
#if QT_CONFIG(cursor)
void setCursor(struct wl_buffer *buffer, struct wl_cursor_image *image, qreal dpr);
void setCursor(const QSharedPointer<QWaylandBuffer> &buffer, const QPoint &hotSpot, qreal dpr);
QWaylandCursorTheme *loadCursorTheme(qreal devicePixelRatio);
QWaylandCursor *waylandCursor();
QWaylandCursorTheme *loadCursorTheme(const QString &name, int pixelSize);
#endif
struct wl_display *wl_display() const { return mDisplay; }
struct ::wl_registry *wl_registry() { return object(); }
@ -210,7 +210,8 @@ private:
QList<Listener> mRegistryListeners;
QWaylandIntegration *mWaylandIntegration = nullptr;
#if QT_CONFIG(cursor)
QMap<int, QWaylandCursorTheme *> mCursorThemesBySize;
QMap<std::pair<QString, int>, QWaylandCursorTheme *> mCursorThemes; // theme name and size
QScopedPointer<QWaylandCursor> mCursor;
#endif
#if QT_CONFIG(wayland_datadevice)
QScopedPointer<QWaylandDataDeviceManager> mDndSelectionHandler;

View File

@ -170,13 +170,13 @@ 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 *p)
: mParent(p)
QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat)
: mParent(seat)
{
}
@ -188,6 +188,191 @@ QWaylandInputDevice::Pointer::~Pointer()
wl_pointer_destroy(object());
}
#if QT_CONFIG(cursor)
class CursorSurface : public QObject, public QtWayland::wl_surface
{
public:
explicit CursorSurface(QWaylandInputDevice::Pointer *pointer, QWaylandDisplay *display)
: m_pointer(pointer)
{
init(display->createSurface(this));
//TODO: When we upgrade to libwayland 1.10, use wl_surface_get_version instead.
m_version = display->compositorVersion();
connect(qApp, &QGuiApplication::screenRemoved, this, [this](QScreen *screen) {
int oldScale = outputScale();
if (!m_screens.removeOne(static_cast<QWaylandScreen *>(screen->handle())))
return;
if (outputScale() != oldScale)
m_pointer->updateCursor();
});
}
void hide()
{
uint serial = m_pointer->mEnterSerial;
Q_ASSERT(serial);
m_pointer->set_cursor(serial, nullptr, 0, 0);
m_setSerial = 0;
}
// Size and hotspot are in surface coordinates
void update(wl_buffer *buffer, const QPoint &hotspot, const QSize &size, int bufferScale)
{
// Calling code needs to ensure buffer scale is supported if != 1
Q_ASSERT(bufferScale == 1 || m_version >= 3);
auto enterSerial = m_pointer->mEnterSerial;
if (m_setSerial < enterSerial || m_hotspot != hotspot) {
m_pointer->set_cursor(m_pointer->mEnterSerial, object(), hotspot.x(), hotspot.y());
m_setSerial = enterSerial;
m_hotspot = hotspot;
}
if (m_version >= 3)
set_buffer_scale(bufferScale);
attach(buffer, 0, 0);
damage(0, 0, size.width(), size.height());
commit();
}
int outputScale() const
{
int scale = 0;
for (auto *screen : m_screens)
scale = qMax(scale, screen->scale());
return scale;
}
protected:
void surface_enter(struct ::wl_output *output) override
{
int oldScale = outputScale();
auto *screen = QWaylandScreen::fromWlOutput(output);
if (m_screens.contains(screen))
return;
m_screens.append(screen);
if (outputScale() != oldScale)
m_pointer->updateCursor();
}
void surface_leave(struct ::wl_output *output) override
{
int oldScale = outputScale();
auto *screen = QWaylandScreen::fromWlOutput(output);
if (!m_screens.removeOne(screen))
return;
if (outputScale() != oldScale)
m_pointer->updateCursor();
}
private:
QWaylandInputDevice::Pointer *m_pointer = nullptr;
uint m_version = 0;
uint m_setSerial = 0;
QPoint m_hotspot;
QVector<QWaylandScreen *> m_screens;
};
QString QWaylandInputDevice::Pointer::cursorThemeName() const
{
static QString themeName = qEnvironmentVariable("XCURSOR_THEME", QStringLiteral("default"));
return themeName;
}
int QWaylandInputDevice::Pointer::cursorSize() const
{
constexpr int defaultCursorSize = 32;
static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
return xCursorSize > 0 ? xCursorSize : defaultCursorSize;
}
int QWaylandInputDevice::Pointer::idealCursorScale() const
{
// set_buffer_scale is not supported on earlier versions
if (seat()->mQDisplay->compositorVersion() < 3)
return 1;
if (auto *s = mCursor.surface.data()) {
if (s->outputScale() > 0)
return s->outputScale();
}
return seat()->mCursor.fallbackOutputScale;
}
void QWaylandInputDevice::Pointer::updateCursorTheme()
{
int scale = idealCursorScale();
int pixelSize = cursorSize() * scale;
auto *display = seat()->mQDisplay;
mCursor.theme = display->loadCursorTheme(cursorThemeName(), pixelSize);
if (auto *arrow = mCursor.theme->cursorImage(Qt::ArrowCursor)) {
int arrowPixelSize = qMax(arrow->width, arrow->height); // Not all cursor themes are square
while (scale > 1 && arrowPixelSize / scale < cursorSize())
--scale;
} else {
qWarning(lcQpaWayland) << "Cursor theme does not support the arrow cursor";
}
mCursor.themeBufferScale = scale;
}
void QWaylandInputDevice::Pointer::updateCursor()
{
if (mEnterSerial == 0)
return;
auto shape = seat()->mCursor.shape;
if (shape == Qt::BlankCursor) {
if (mCursor.surface)
mCursor.surface->hide();
return;
}
if (shape == Qt::BitmapCursor) {
auto buffer = seat()->mCursor.bitmapBuffer;
if (!buffer) {
qCWarning(lcQpaWayland) << "No buffer for bitmap cursor, can't set cursor";
return;
}
auto hotspot = seat()->mCursor.hotspot;
int bufferScale = seat()->mCursor.bitmapScale;
getOrCreateCursorSurface()->update(buffer->buffer(), hotspot, buffer->size(), bufferScale);
return;
}
if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale)
updateCursorTheme();
// Set from shape using theme
if (struct ::wl_cursor_image *image = mCursor.theme->cursorImage(shape)) {
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
int bufferScale = mCursor.themeBufferScale;
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
QSize size = QSize(image->width, image->height) / bufferScale;
getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale);
return;
}
qCWarning(lcQpaWayland) << "Unable to change to cursor" << shape;
}
CursorSurface *QWaylandInputDevice::Pointer::getOrCreateCursorSurface()
{
if (!mCursor.surface)
mCursor.surface.reset(new CursorSurface(this, seat()->mQDisplay));
return mCursor.surface.get();
}
#endif // QT_CONFIG(cursor)
QWaylandInputDevice::Touch::Touch(QWaylandInputDevice *p)
: mParent(p)
{
@ -276,12 +461,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)
@ -325,7 +504,7 @@ QWaylandWindow *QWaylandInputDevice::pointerFocus() const
QWaylandWindow *QWaylandInputDevice::keyboardFocus() const
{
return mKeyboard ? mKeyboard->mFocus : nullptr;
return mKeyboard ? mKeyboard->focusWindow() : nullptr;
}
QWaylandWindow *QWaylandInputDevice::touchFocus() const
@ -361,87 +540,33 @@ Qt::KeyboardModifiers QWaylandInputDevice::Keyboard::modifiers() const
}
#if QT_CONFIG(cursor)
uint32_t QWaylandInputDevice::cursorSerial() const
void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer<QWaylandBuffer> &cachedBuffer, int fallbackOutputScale)
{
CursorState oldCursor = mCursor;
mCursor = CursorState(); // Clear any previous state
mCursor.shape = cursor ? cursor->shape() : Qt::ArrowCursor;
mCursor.hotspot = cursor ? cursor->hotSpot() : QPoint();
mCursor.fallbackOutputScale = fallbackOutputScale;
if (mCursor.shape == Qt::BitmapCursor) {
mCursor.bitmapBuffer = cachedBuffer ? cachedBuffer : QWaylandCursor::cursorBitmapBuffer(mQDisplay, cursor);
qreal dpr = cursor->pixmap().devicePixelRatio();
mCursor.bitmapScale = int(dpr); // Wayland doesn't support fractional buffer scale
// If there was a fractional part of the dpr, we need to scale the hotspot accordingly
if (mCursor.bitmapScale < dpr)
mCursor.hotspot *= dpr / mCursor.bitmapScale;
}
// Return early if setCursor was called redundantly (mostly happens from decorations)
if (mCursor.shape != Qt::BitmapCursor
&& mCursor.shape == oldCursor.shape
&& mCursor.hotspot == oldCursor.hotspot
&& mCursor.fallbackOutputScale == oldCursor.fallbackOutputScale) {
return;
}
if (mPointer)
return mPointer->mCursorSerial;
return 0;
}
void QWaylandInputDevice::setCursor(Qt::CursorShape newShape, QWaylandScreen *screen)
{
struct wl_cursor_image *image = screen->waylandCursor()->cursorImage(newShape);
if (!image) {
return;
}
struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);
setCursor(buffer, image, screen->devicePixelRatio());
}
void QWaylandInputDevice::setCursor(const QCursor &cursor, QWaylandScreen *screen)
{
if (mPointer->mCursorSerial >= mPointer->mEnterSerial && (cursor.shape() != Qt::BitmapCursor && cursor.shape() == mPointer->mCursorShape))
return;
mPointer->mCursorShape = cursor.shape();
if (cursor.shape() == Qt::BitmapCursor) {
setCursor(screen->waylandCursor()->cursorBitmapImage(&cursor), cursor.hotSpot(), screen->devicePixelRatio());
return;
}
setCursor(cursor.shape(), screen);
}
void QWaylandInputDevice::setCursor(struct wl_buffer *buffer, struct wl_cursor_image *image, int bufferScale)
{
if (image) {
// Convert from pixel coordinates to surface coordinates
QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale;
QSize size = QSize(image->width, image->height) / bufferScale;
setCursor(buffer, hotspot, size, bufferScale);
} else {
setCursor(buffer, QPoint(), QSize(), bufferScale);
}
}
// size and hotspot are in surface coordinates
void QWaylandInputDevice::setCursor(struct wl_buffer *buffer, const QPoint &hotSpot, const QSize &size, int bufferScale)
{
if (mCaps & WL_SEAT_CAPABILITY_POINTER) {
bool force = mPointer->mEnterSerial > mPointer->mCursorSerial;
if (!force && mPointer->mCursorBuffer == buffer)
return;
mPixmapCursor.clear();
mPointer->mCursorSerial = mPointer->mEnterSerial;
mPointer->mCursorBuffer = buffer;
/* Hide cursor */
if (!buffer)
{
mPointer->set_cursor(mPointer->mEnterSerial, nullptr, 0, 0);
return;
}
if (!pointerSurface)
pointerSurface = mQDisplay->createSurface(this);
mPointer->set_cursor(mPointer->mEnterSerial, pointerSurface,
hotSpot.x(), hotSpot.y());
wl_surface_attach(pointerSurface, buffer, 0, 0);
if (mQDisplay->compositorVersion() >= 3)
wl_surface_set_buffer_scale(pointerSurface, bufferScale);
wl_surface_damage(pointerSurface, 0, 0, size.width(), size.height());
wl_surface_commit(pointerSurface);
}
}
void QWaylandInputDevice::setCursor(const QSharedPointer<QWaylandBuffer> &buffer, const QPoint &hotSpot, int bufferScale)
{
setCursor(buffer->buffer(), hotSpot, buffer->size(), bufferScale);
mPixmapCursor = buffer;
mPointer->updateCursor();
}
#endif
@ -461,7 +586,16 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf
return;
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
if (mFocus) {
qWarning(lcQpaWayland) << "The compositor sent a wl_pointer.enter event before sending a"
<< "leave event first, this is not allowed by the wayland protocol"
<< "attempting to work around it by invalidating the current focus";
invalidateFocus();
}
mFocus = window;
connect(mFocus, &QWaylandWindow::wlSurfaceDestroyed, this, &Pointer::handleFocusDestroyed);
mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy));
mGlobalPos = window->window()->mapToGlobal(mSurfacePos.toPoint());
@ -470,7 +604,7 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf
#if QT_CONFIG(cursor)
// Depends on mEnterSerial being updated
window->window()->setCursor(window->window()->cursor());
updateCursor();
#endif
QWaylandWindow *grab = QWaylandWindow::mouseGrab();
@ -498,7 +632,8 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos));
}
mFocus = nullptr;
invalidateFocus();
mButtons = Qt::NoButton;
mParent->mTime = time;
@ -627,6 +762,13 @@ void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time
setFrameEvent(new ReleaseEvent(window, time, pos, global, mButtons, mParent->modifiers()));
}
void QWaylandInputDevice::Pointer::invalidateFocus()
{
disconnect(mFocus, &QWaylandWindow::wlSurfaceDestroyed, this, &Pointer::handleFocusDestroyed);
mFocus = nullptr;
mEnterSerial = 0;
}
void QWaylandInputDevice::Pointer::releaseButtons()
{
mButtons = Qt::NoButton;
@ -932,12 +1074,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);
}
@ -945,18 +1094,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,
@ -981,7 +1132,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.
@ -1061,14 +1212,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
@ -1076,7 +1228,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
@ -1085,6 +1237,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->wlSurface() == 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,
@ -1186,7 +1359,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

@ -88,6 +88,10 @@ class QWaylandWindow;
class QWaylandDisplay;
class QWaylandDataDevice;
class QWaylandTextInput;
#if QT_CONFIG(cursor)
class QWaylandCursorTheme;
class CursorSurface;
#endif
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice
: public QObject
@ -107,12 +111,8 @@ public:
struct ::wl_seat *wl_seat() { return QtWayland::wl_seat::object(); }
#if QT_CONFIG(cursor)
void setCursor(const QCursor &cursor, QWaylandScreen *screen);
void setCursor(struct wl_buffer *buffer, struct ::wl_cursor_image *image, int bufferScale);
void setCursor(struct wl_buffer *buffer, const QPoint &hotSpot, const QSize &size, int bufferScale);
void setCursor(const QSharedPointer<QWaylandBuffer> &buffer, const QPoint &hotSpot, int bufferScale);
void setCursor(const QCursor *cursor, const QSharedPointer<QWaylandBuffer> &cachedBuffer = {}, int fallbackOutputScale = 1);
#endif
void handleWindowDestroyed(QWaylandWindow *window);
void handleEndDrag();
#if QT_CONFIG(wayland_datadevice)
@ -134,22 +134,27 @@ public:
Qt::KeyboardModifiers modifiers() const;
uint32_t serial() const;
uint32_t cursorSerial() const;
virtual Keyboard *createKeyboard(QWaylandInputDevice *device);
virtual Pointer *createPointer(QWaylandInputDevice *device);
virtual Touch *createTouch(QWaylandInputDevice *device);
private:
void setCursor(Qt::CursorShape cursor, QWaylandScreen *screen);
QWaylandDisplay *mQDisplay = nullptr;
struct wl_display *mDisplay = nullptr;
int mVersion;
uint32_t mCaps = 0;
struct wl_surface *pointerSurface = nullptr;
#if QT_CONFIG(cursor)
struct CursorState {
QSharedPointer<QWaylandBuffer> bitmapBuffer; // not used with shape cursors
int bitmapScale = 1;
Qt::CursorShape shape = Qt::ArrowCursor;
int fallbackOutputScale = 1;
QPoint hotspot;
} mCursor;
#endif
#if QT_CONFIG(wayland_datadevice)
QWaylandDataDevice *mDataDevice = nullptr;
@ -169,8 +174,6 @@ private:
QTouchDevice *mTouchDevice = nullptr;
QSharedPointer<QWaylandBuffer> mPixmapCursor;
friend class QWaylandTouchExtension;
friend class QWaylandQtKeyExtension;
};
@ -189,7 +192,7 @@ public:
Keyboard(QWaylandInputDevice *p);
~Keyboard() override;
void stopRepeat();
QWaylandWindow *focusWindow() const;
void keyboard_keymap(uint32_t format,
int32_t fd,
@ -209,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;
@ -234,6 +237,8 @@ public:
private slots:
void repeatKey();
void handleFocusDestroyed();
void handleFocusLost();
private:
#if QT_CONFIG(xkbcommon)
@ -245,13 +250,23 @@ private:
};
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Pointer : public QtWayland::wl_pointer
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice::Pointer : public QObject, public QtWayland::wl_pointer
{
Q_OBJECT
public:
Pointer(QWaylandInputDevice *p);
explicit Pointer(QWaylandInputDevice *seat);
~Pointer() override;
#if QT_CONFIG(cursor)
QString cursorThemeName() const;
int cursorSize() const; // in surface coordinates
int idealCursorScale() const;
void updateCursorTheme();
void updateCursor();
CursorSurface *getOrCreateCursorSurface();
#endif
QWaylandInputDevice *seat() const { return mParent; }
protected:
void pointer_enter(uint32_t serial, struct wl_surface *surface,
wl_fixed_t sx, wl_fixed_t sy) override;
void pointer_leave(uint32_t time, struct wl_surface *surface) override;
@ -267,13 +282,24 @@ public:
void pointer_axis_discrete(uint32_t axis, int32_t value) override;
void pointer_frame() override;
private slots:
void handleFocusDestroyed() { invalidateFocus(); }
private:
void invalidateFocus();
public:
void releaseButtons();
QWaylandInputDevice *mParent = nullptr;
QPointer<QWaylandWindow> mFocus;
uint32_t mEnterSerial = 0;
#if QT_CONFIG(cursor)
uint32_t mCursorSerial = 0;
struct {
QWaylandCursorTheme *theme = nullptr;
int themeBufferScale = 0;
QScopedPointer<CursorSurface> surface;
} mCursor;
#endif
QPointF mSurfacePos;
QPointF mGlobalPos;

View File

@ -176,19 +176,10 @@ qreal QWaylandScreen::refreshRate() const
}
#if QT_CONFIG(cursor)
QPlatformCursor *QWaylandScreen::cursor() const
{
return const_cast<QWaylandScreen *>(this)->waylandCursor();
return mWaylandDisplay->waylandCursor();
}
QWaylandCursor *QWaylandScreen::waylandCursor()
{
if (!mWaylandCursor)
mWaylandCursor.reset(new QWaylandCursor(this));
return mWaylandCursor.data();
}
#endif // QT_CONFIG(cursor)
QWaylandScreen * QWaylandScreen::waylandScreenFromWindow(QWindow *window)

View File

@ -98,7 +98,6 @@ public:
#if QT_CONFIG(cursor)
QPlatformCursor *cursor() const override;
QWaylandCursor *waylandCursor();
#endif
uint32_t outputId() const { return m_outputId; }

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) {
@ -396,14 +389,9 @@ void QWaylandWindow::setVisible(bool visible)
// QWaylandShmBackingStore::beginPaint().
} else {
sendExposeEvent(QRect());
// when flushing the event queue, it could contain a close event, in which
// case 'this' will be deleted. When that happens, we must abort right away.
QPointer<QWaylandWindow> deleteGuard(this);
QWindowSystemInterface::flushWindowSystemEvents();
if (!deleteGuard.isNull() && window()->type() == Qt::Popup)
if (window()->type() == Qt::Popup)
closePopups(this);
if (!deleteGuard.isNull())
reset();
reset();
}
}
@ -970,7 +958,8 @@ void QWaylandWindow::handleScreenChanged()
#if QT_CONFIG(cursor)
void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &cursor)
{
device->setCursor(cursor, waylandScreen());
int fallbackBufferScale = int(devicePixelRatio());
device->setCursor(&cursor, {}, fallbackBufferScale);
}
void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device)
@ -984,16 +973,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

@ -154,7 +154,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;

View File

@ -47,7 +47,7 @@ public:
add<DataDeviceManager>(dataDeviceVersion);
});
}
DataDevice *dataDevice() { return get<Seat>()->dataDevice(); }
DataDevice *dataDevice() { return get<DataDeviceManager>()->deviceFor(get<Seat>()); }
};
class tst_datadevicev1 : public QObject, private DataDeviceCompositor
@ -58,6 +58,8 @@ private slots:
void initTestCase();
void pasteAscii();
void pasteUtf8();
void destroysPreviousSelection();
void destroysSelectionWithSurface();
};
void tst_datadevicev1::initTestCase()
@ -69,7 +71,8 @@ void tst_datadevicev1::initTestCase()
QCOMPOSITOR_TRY_VERIFY(keyboard());
QCOMPOSITOR_TRY_VERIFY(dataDevice());
QCOMPOSITOR_TRY_COMPARE(dataDevice()->resource()->version(), dataDeviceVersion);
QCOMPOSITOR_TRY_VERIFY(dataDevice()->resourceMap().contains(client()));
QCOMPOSITOR_TRY_COMPARE(dataDevice()->resourceMap().value(client())->version(), dataDeviceVersion);
}
void tst_datadevicev1::pasteAscii()
@ -86,7 +89,8 @@ void tst_datadevicev1::pasteAscii()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([&] {
auto *offer = new DataOffer(client(), dataDeviceVersion); // Cleaned up by destroy_resource
auto *client = xdgSurface()->resource()->client();
auto *offer = dataDevice()->sendDataOffer(client, {"text/plain"});
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
QFile file;
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
@ -94,16 +98,14 @@ void tst_datadevicev1::pasteAscii()
file.write(QByteArray("normal ascii"));
file.close();
});
dataDevice()->sendDataOffer(offer);
offer->send_offer("text/plain");
dataDevice()->sendSelection(offer);
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
pointer()->sendEnter(surface, {32, 32});
pointer()->sendButton(client(), BTN_LEFT, 1);
pointer()->sendButton(client(), BTN_LEFT, 0);
pointer()->sendButton(client, BTN_LEFT, 1);
pointer()->sendButton(client, BTN_LEFT, 0);
});
QTRY_COMPARE(window.m_text, "normal ascii");
}
@ -122,7 +124,8 @@ void tst_datadevicev1::pasteUtf8()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([&] {
auto *offer = new DataOffer(client(), dataDeviceVersion); // Cleaned up by destroy_resource
auto *client = xdgSurface()->resource()->client();
auto *offer = dataDevice()->sendDataOffer(client, {"text/plain", "text/plain;charset=utf-8"});
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
QFile file;
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
@ -130,20 +133,81 @@ void tst_datadevicev1::pasteUtf8()
file.write(QByteArray("face with tears of joy: 😂"));
file.close();
});
dataDevice()->sendDataOffer(offer);
offer->send_offer("text/plain");
offer->send_offer("text/plain;charset=utf-8");
dataDevice()->sendSelection(offer);
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
pointer()->sendEnter(surface, {32, 32});
pointer()->sendButton(client(), BTN_LEFT, 1);
pointer()->sendButton(client(), BTN_LEFT, 0);
pointer()->sendButton(client, BTN_LEFT, 1);
pointer()->sendButton(client, BTN_LEFT, 0);
});
QTRY_COMPARE(window.m_text, "face with tears of joy: 😂");
}
void tst_datadevicev1::destroysPreviousSelection()
{
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
// When the client receives a selection event, it is required to destroy the previous offer
exec([&] {
QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0);
auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"});
dataDevice()->sendSelection(offer);
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1);
});
exec([&] {
auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"});
dataDevice()->sendSelection(offer);
QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 2);
});
// Verify the first offer gets destroyed
QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 1);
exec([&] {
auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"});
dataDevice()->sendSelection(offer);
auto *surface = xdgSurface()->m_surface;
keyboard()->sendLeave(surface);
});
// Clients are required to destroy their offer when losing keyboard focus
QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0);
}
void tst_datadevicev1::destroysSelectionWithSurface()
{
auto *window = new QRasterWindow;
window->resize(64, 64);
window->show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
// When the client receives a selection event, it is required to destroy the previous offer
exec([&] {
QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 0);
auto *offer = dataDevice()->sendDataOffer(client(), {"text/plain"});
dataDevice()->sendSelection(offer);
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
QCOMPARE(dataDevice()->m_sentSelectionOffers.size(), 1);
});
// Ping to make sure we receive the wl_keyboard enter and leave events, before destroying the
// surface. Otherwise, the client will receive enter and leave events with a destroyed (null)
// surface, which is not what we are trying to test for here.
xdgPingAndWaitForPong();
window->destroy();
QCOMPOSITOR_TRY_COMPARE(dataDevice()->m_sentSelectionOffers.size(), 0);
}
QCOMPOSITOR_TEST_MAIN(tst_datadevicev1)
#include "tst_datadevicev1.moc"

View File

@ -1,4 +1,9 @@
include (../shared/shared.pri)
qtConfig(cursor) {
QMAKE_USE += wayland-cursor
QT += gui-private
}
TARGET = tst_seatv4
SOURCES += tst_seatv4.cpp

View File

@ -30,6 +30,12 @@
#include <QtGui/QRasterWindow>
#include <QtGui/QOpenGLWindow>
#if QT_CONFIG(cursor)
#include <wayland-cursor.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtWaylandClient/private/qwaylanddisplay_p.h>
#include <QtWaylandClient/private/qwaylandintegration_p.h>
#endif
using namespace MockCompositor;
@ -59,16 +65,22 @@ class tst_seatv4 : public QObject, private SeatV4Compositor
private slots:
void cleanup();
void bindsToSeat();
void keyboardKeyPress();
#if QT_CONFIG(cursor)
void createsPointer();
void setsCursorOnEnter();
void usesEnterSerial();
void focusDestruction();
void mousePress();
void simpleAxis_data();
void simpleAxis();
void invalidPointerEvents();
void scaledCursor();
void keyboardKeyPress();
void unscaledFallbackCursor();
void bitmapCursor();
void hidpiBitmapCursor();
void hidpiBitmapCursorNonInt();
#endif
};
void tst_seatv4::cleanup()
@ -83,6 +95,31 @@ void tst_seatv4::bindsToSeat()
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 4);
}
void tst_seatv4::keyboardKeyPress()
{
class Window : public QRasterWindow {
public:
void keyPressEvent(QKeyEvent *) override { m_pressed = true; }
bool m_pressed = false;
};
Window window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
uint keyCode = 80; // arbitrarily chosen
exec([&] {
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface);
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed);
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released);
});
QTRY_VERIFY(window.m_pressed);
}
#if QT_CONFIG(cursor)
void tst_seatv4::createsPointer()
{
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
@ -97,7 +134,7 @@ void tst_seatv4::setsCursorOnEnter()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
}
void tst_seatv4::usesEnterSerial()
@ -111,12 +148,43 @@ void tst_seatv4::usesEnterSerial()
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
});
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 1);
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
}
void tst_seatv4::focusDestruction()
{
QSignalSpy setCursorSpy(exec([=] { return pointer(); }), &Pointer::setCursor);
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
// Setting a cursor now is not allowed since there has been no enter event
QCOMPARE(setCursorSpy.count(), 0);
uint enterSerial = exec([=] {
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
});
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QTRY_COMPARE(setCursorSpy.count(), 1);
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
// Destroy the focus
window.close();
QRasterWindow window2;
window2.resize(64, 64);
window2.show();
window2.setCursor(Qt::WaitCursor);
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
// Setting a cursor now is not allowed since there has been no enter event
xdgPingAndWaitForPong();
QCOMPARE(setCursorSpy.count(), 0);
}
void tst_seatv4::mousePress()
{
class Window : public QRasterWindow {
@ -253,9 +321,42 @@ void tst_seatv4::invalidPointerEvents()
xdgPingAndWaitForPong();
}
static bool supportsCursorSize(uint size, wl_shm *shm)
{
auto *theme = wl_cursor_theme_load(qgetenv("XCURSOR_THEME"), size, shm);
if (!theme)
return false;
constexpr std::array<const char *, 4> names{"left_ptr", "default", "left_arrow", "top_left_arrow"};
for (const char *name : names) {
if (auto *cursor = wl_cursor_theme_get_cursor(theme, name)) {
auto *image = cursor->images[0];
return image->width == image->height && image->width == size;
}
}
return false;
}
static bool supportsCursorSizes(const QVector<uint> &sizes)
{
auto *waylandIntegration = static_cast<QtWaylandClient::QWaylandIntegration *>(QGuiApplicationPrivate::platformIntegration());
wl_shm *shm = waylandIntegration->display()->shm()->object();
return std::all_of(sizes.begin(), sizes.end(), [=](uint size) {
return supportsCursorSize(size, shm);
});
}
static uint defaultCursorSize() {
const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE");
return xCursorSize > 0 ? uint(xCursorSize) : 32;
}
void tst_seatv4::scaledCursor()
{
QSKIP("Currently broken and should be fixed");
const uint defaultSize = defaultCursorSize();
if (!supportsCursorSizes({defaultSize, defaultSize * 2}))
QSKIP("Cursor themes with default size and 2x default size not found.");
// Add a highdpi output
exec([&] {
OutputData d;
@ -270,47 +371,190 @@ void tst_seatv4::scaledCursor()
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()->m_committed.buffer);
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.bufferScale, 1);
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
QSize unscaledPixelSize = exec([=] {
return pointer()->cursorSurface()->m_committed.buffer->size();
return cursorSurface()->m_committed.buffer->size();
});
exec([=] {
auto *surface = pointer()->cursorSurface();
auto *surface = cursorSurface();
surface->sendEnter(getAll<Output>()[1]);
surface->sendLeave(getAll<Output>()[0]);
});
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2);
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size(), unscaledPixelSize * 2);
// Remove the extra output to clean up for the next test
exec([&] { remove(output(1)); });
}
void tst_seatv4::keyboardKeyPress()
void tst_seatv4::unscaledFallbackCursor()
{
class Window : public QRasterWindow {
public:
void keyPressEvent(QKeyEvent *) override { m_pressed = true; }
bool m_pressed = false;
};
const uint defaultSize = defaultCursorSize();
if (!supportsCursorSizes({defaultSize}))
QSKIP("Default cursor size not supported");
Window window;
const int screens = 4; // with scales 1, 2, 4, 8
exec([=] {
for (int i = 1; i < screens; ++i) {
OutputData d;
d.scale = int(qPow(2, i));
d.position = {1920 * i, 0};
add<Output>(d);
}
});
QRasterWindow window;
window.resize(64, 64);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
uint keyCode = 80; // arbitrarily chosen
exec([&] {
auto *surface = xdgSurface()->m_surface;
keyboard()->sendEnter(surface);
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed);
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
QSize unscaledPixelSize = exec([=] {
return cursorSurface()->m_committed.buffer->size();
});
QTRY_VERIFY(window.m_pressed);
QCOMPARE(unscaledPixelSize.width(), int(defaultSize));
QCOMPARE(unscaledPixelSize.height(), int(defaultSize));
for (int i = 1; i < screens; ++i) {
exec([=] {
auto *surface = cursorSurface();
surface->sendEnter(getAll<Output>()[i]);
surface->sendLeave(getAll<Output>()[i-1]);
});
xdgPingAndWaitForPong(); // Give the client a chance to mess up
// Surface size (buffer size / scale) should stay constant
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.buffer->size() / cursorSurface()->m_committed.bufferScale, unscaledPixelSize);
}
// Remove the extra outputs to clean up for the next test
exec([&] { while (auto *o = output(1)) remove(o); });
}
void tst_seatv4::bitmapCursor()
{
// Add a highdpi output
exec([&] {
OutputData d;
d.scale = 2;
d.position = {1920, 0};
add<Output>(d);
});
QRasterWindow window;
window.resize(64, 64);
QPixmap pixmap(24, 24);
pixmap.setDevicePixelRatio(1);
QPoint hotspot(12, 12); // In device pixel coordinates
QCursor cursor(pixmap, hotspot.x(), hotspot.y());
window.setCursor(cursor);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24));
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12));
exec([=] {
auto *surface = cursorSurface();
surface->sendEnter(getAll<Output>()[1]);
surface->sendLeave(getAll<Output>()[0]);
});
xdgPingAndWaitForPong();
// Everything should remain the same, the cursor still has dpr 1
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(24, 24));
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12));
// Remove the extra output to clean up for the next test
exec([&] { remove(getAll<Output>()[1]); });
}
void tst_seatv4::hidpiBitmapCursor()
{
// Add a highdpi output
exec([&] {
OutputData d;
d.scale = 2;
d.position = {1920, 0};
add<Output>(d);
});
QRasterWindow window;
window.resize(64, 64);
QPixmap pixmap(48, 48);
pixmap.setDevicePixelRatio(2);
QPoint hotspot(12, 12); // In device pixel coordinates
QCursor cursor(pixmap, hotspot.x(), hotspot.y());
window.setCursor(cursor);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48));
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2);
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12));
exec([=] {
auto *surface = cursorSurface();
surface->sendEnter(getAll<Output>()[1]);
surface->sendLeave(getAll<Output>()[0]);
});
xdgPingAndWaitForPong();
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2);
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(48, 48));
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(12, 12));
// Remove the extra output to clean up for the next test
exec([&] { remove(getAll<Output>()[1]); });
}
void tst_seatv4::hidpiBitmapCursorNonInt()
{
QRasterWindow window;
window.resize(64, 64);
QPixmap pixmap(100, 100);
pixmap.setDevicePixelRatio(2.5); // dpr width is now 100 / 2.5 = 40
QPoint hotspot(20, 20); // In device pixel coordinates (middle of buffer)
QCursor cursor(pixmap, hotspot.x(), hotspot.y());
window.setCursor(cursor);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.buffer->size(), QSize(100, 100));
QCOMPOSITOR_COMPARE(cursorSurface()->m_committed.bufferScale, 2);
// Verify that the hotspot was scaled correctly
// Surface size is now 100 / 2 = 50, so the middle should be at 25 in surface coordinates
QCOMPOSITOR_COMPARE(pointer()->m_hotspot, QPoint(25, 25));
}
#endif // QT_CONFIG(cursor)
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
#include "tst_seatv4.moc"

View File

@ -258,18 +258,20 @@ uint Pointer::sendEnter(Surface *surface, const QPointF &position)
{
wl_fixed_t x = wl_fixed_from_double(position.x());
wl_fixed_t y = wl_fixed_from_double(position.y());
m_enterSerial = m_seat->m_compositor->nextSerial();
uint serial = m_seat->m_compositor->nextSerial();
m_enterSerials << serial;
m_cursorRole = nullptr; // According to the protocol, the pointer image is undefined after enter
wl_client *client = surface->resource()->client();
const auto pointerResources = resourceMap().values(client);
for (auto *r : pointerResources)
wl_pointer::send_enter(r->handle, m_enterSerial, surface->resource()->handle, x ,y);
return m_enterSerial;
wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y);
return serial;
}
uint Pointer::sendLeave(Surface *surface)
{
m_enterSerial = 0;
uint serial = m_seat->m_compositor->nextSerial();
wl_client *client = surface->resource()->client();
@ -348,20 +350,24 @@ void Pointer::sendFrame(wl_client *client)
void Pointer::pointer_set_cursor(Resource *resource, uint32_t serial, wl_resource *surface, int32_t hotspot_x, int32_t hotspot_y)
{
Q_UNUSED(resource);
Q_UNUSED(hotspot_x);
Q_UNUSED(hotspot_y);
auto *s = fromResource<Surface>(surface);
QVERIFY(s);
if (s->m_role) {
auto *cursorRole = CursorRole::fromSurface(s);
QVERIFY(cursorRole);
QVERIFY(cursorRole == m_cursorRole);
m_cursorRole = CursorRole::fromSurface(s);
QVERIFY(m_cursorRole);
} else {
m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
s->m_role = m_cursorRole;
}
// QCOMPARE(serial, m_enterSerial); //TODO: uncomment when this bug is fixed
// Directly checking the last serial would be racy, we may just have sent leaves/enters which
// the client hasn't yet seen. Instead just check if the serial matches an enter serial since
// the last time the client sent a set_cursor request.
QVERIFY(m_enterSerials.contains(serial));
while (m_enterSerials.first() < serial) { m_enterSerials.removeFirst(); }
m_hotspot = QPoint(hotspot_x, hotspot_y);
emit setCursor(serial);
}

View File

@ -250,9 +250,6 @@ public:
Keyboard* m_keyboard = nullptr;
QVector<Keyboard *> m_oldKeyboards;
DataDevice *dataDevice() { return m_dataDevice.data(); }
QScopedPointer<DataDevice> m_dataDevice;
uint m_capabilities = 0;
protected:
@ -288,7 +285,8 @@ public:
void sendFrame(wl_client *client);
Seat *m_seat = nullptr;
uint m_enterSerial = 0;
QVector<uint> m_enterSerials;
QPoint m_hotspot;
signals:
void setCursor(uint serial); //TODO: add arguments?

View File

@ -30,29 +30,60 @@
namespace MockCompositor {
bool DataDeviceManager::isClean()
{
for (auto *device : qAsConst(m_dataDevices)) {
// The client should not leak selection offers, i.e. if this fails, there is a missing
// data_offer.destroy request
if (!device->m_sentSelectionOffers.empty())
return false;
}
return true;
}
DataDevice *DataDeviceManager::deviceFor(Seat *seat)
{
Q_ASSERT(seat);
if (auto *device = m_dataDevices.value(seat, nullptr))
return device;
auto *device = new DataDevice(this, seat);
m_dataDevices[seat] = device;
return device;
}
void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, wl_resource *seatResource)
{
auto *seat = fromResource<Seat>(seatResource);
QVERIFY(seat);
QVERIFY(!seat->m_dataDevice);
seat->m_dataDevice.reset(new DataDevice(resource->client(), id, resource->version()));
auto *device = deviceFor(seat);
device->add(resource->client(), id, resource->version());
}
DataDevice::~DataDevice()
{
// If the client hasn't deleted the wayland object, just ignore subsequent events
if (auto *r = resource()->handle)
wl_resource_set_implementation(r, nullptr, nullptr, nullptr);
// If the client(s) hasn't deleted the wayland object, just ignore subsequent events
for (auto *r : resourceMap())
wl_resource_set_implementation(r->handle, nullptr, nullptr, nullptr);
}
void DataDevice::sendDataOffer(DataOffer *offer)
DataOffer *DataDevice::sendDataOffer(wl_client *client, const QStringList &mimeTypes)
{
wl_data_device::send_data_offer(offer->resource()->handle);
Q_ASSERT(client);
auto *offer = new DataOffer(this, client, m_manager->m_version);
for (auto *resource : resourceMap().values(client))
wl_data_device::send_data_offer(resource->handle, offer->resource()->handle);
for (const auto &mimeType : mimeTypes)
offer->send_offer(mimeType);
return offer;
}
void DataDevice::sendSelection(DataOffer *offer)
{
wl_data_device::send_selection(offer->resource()->handle);
auto *client = offer->resource()->client();
for (auto *resource : resourceMap().values(client))
wl_data_device::send_selection(resource->handle, offer->resource()->handle);
m_sentSelectionOffers << offer;
}
void DataOffer::data_offer_destroy_resource(Resource *resource)
@ -67,4 +98,11 @@ void DataOffer::data_offer_receive(Resource *resource, const QString &mime_type,
emit receive(mime_type, fd);
}
void DataOffer::data_offer_destroy(QtWaylandServer::wl_data_offer::Resource *resource)
{
bool removed = m_dataDevice->m_sentSelectionOffers.removeOne(this);
QVERIFY(removed);
wl_resource_destroy(resource->handle);
}
} // namespace MockCompositor

View File

@ -42,8 +42,14 @@ class DataDeviceManager : public Global, public QtWaylandServer::wl_data_device_
public:
explicit DataDeviceManager(CoreCompositor *compositor, int version = 1)
: QtWaylandServer::wl_data_device_manager(compositor->m_display, version)
, m_version(version)
{}
QVector<DataDevice *> m_dataDevices;
~DataDeviceManager() override { qDeleteAll(m_dataDevices); }
bool isClean() override;
DataDevice *deviceFor(Seat *seat);
int m_version = 1; // TODO: remove on libwayland upgrade
QMap<Seat *, DataDevice *> m_dataDevices;
protected:
void data_device_manager_get_data_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override;
@ -52,24 +58,42 @@ protected:
class DataDevice : public QtWaylandServer::wl_data_device
{
public:
explicit DataDevice(::wl_client *client, int id, int version)
: QtWaylandServer::wl_data_device(client, id, version)
explicit DataDevice(DataDeviceManager *manager, Seat *seat)
: m_manager(manager)
, m_seat(seat)
{}
~DataDevice() override;
void send_data_offer(::wl_resource *resource) = delete;
void sendDataOffer(DataOffer *offer);
DataOffer *sendDataOffer(::wl_client *client, const QStringList &mimeTypes = {});
DataOffer *sendDataOffer(const QStringList &mimeTypes = {});
void send_selection(::wl_resource *resource) = delete;
void sendSelection(DataOffer *offer);
DataDeviceManager *m_manager = nullptr;
Seat *m_seat = nullptr;
QVector<DataOffer *> m_sentSelectionOffers;
protected:
void data_device_release(Resource *resource) override
{
int removed = m_manager->m_dataDevices.remove(m_seat);
QVERIFY(removed);
wl_resource_destroy(resource->handle);
}
};
class DataOffer : public QObject, public QtWaylandServer::wl_data_offer
{
Q_OBJECT
public:
explicit DataOffer(::wl_client *client, int version)
explicit DataOffer(DataDevice *dataDevice, ::wl_client *client, int version)
: QtWaylandServer::wl_data_offer (client, 0, version)
, m_dataDevice(dataDevice)
{}
DataDevice *m_dataDevice = nullptr;
signals:
void receive(QString mimeType, int fd);
@ -77,7 +101,7 @@ protected:
void data_offer_destroy_resource(Resource *resource) override;
void data_offer_receive(Resource *resource, const QString &mime_type, int32_t fd) override;
// void data_offer_accept(Resource *resource, uint32_t serial, const QString &mime_type) override;
// void data_offer_destroy(Resource *resource) override;
void data_offer_destroy(Resource *resource) override;
};
} // namespace MockCompositor

View File

@ -54,6 +54,7 @@ public:
XdgToplevel *xdgToplevel(int i = 0) { return get<XdgWmBase>()->toplevel(i); }
XdgPopup *xdgPopup(int i = 0) { return get<XdgWmBase>()->popup(i); }
Pointer *pointer() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_pointer; }
Surface *cursorSurface() { auto *p = pointer(); return p ? p->cursorSurface() : nullptr; }
Keyboard *keyboard() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_keyboard; }
uint sendXdgShellPing();
void xdgPingAndWaitForPong();

View File

@ -236,6 +236,7 @@ void XdgPopup::xdg_popup_destroy(Resource *resource) {
}
m_xdgSurface->m_popup = nullptr;
m_parentXdgSurface->m_popups.removeAll(this);
emit destroyRequested();
}
} // namespace MockCompositor

View File

@ -130,8 +130,9 @@ protected:
void xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) override;
};
class XdgPopup : public QtWaylandServer::xdg_popup
class XdgPopup : public QObject, public QtWaylandServer::xdg_popup
{
Q_OBJECT
public:
explicit XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version = 1);
void sendConfigure(const QRect &geometry);
@ -141,6 +142,8 @@ public:
XdgSurface *m_parentXdgSurface = nullptr;
bool m_grabbed = false;
uint m_grabSerial = 0;
signals:
void destroyRequested();
protected:
void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override;
void xdg_popup_destroy(Resource *resource) override;

View File

@ -220,12 +220,14 @@ QSharedPointer<MockSurface> MockCompositor::surface()
{
QSharedPointer<MockSurface> result;
lock();
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
foreach (Impl::Surface *surface, surfaces) {
// we don't want to mistake the cursor surface for a window surface
if (surface->isMapped()) {
result = surface->mockSurface();
break;
{
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
foreach (Impl::Surface *surface, surfaces) {
// we don't want to mistake the cursor surface for a window surface
if (surface->isMapped()) {
result = surface->mockSurface();
break;
}
}
}
unlock();
@ -307,7 +309,7 @@ void *MockCompositor::run(void *data)
{
MockCompositor *controller = static_cast<MockCompositor *>(data);
Impl::Compositor compositor;
Impl::Compositor compositor(controller);
controller->m_compositor = &compositor;
controller->m_waitCondition.wakeOne();
@ -332,8 +334,8 @@ void *MockCompositor::run(void *data)
namespace Impl {
Compositor::Compositor()
: m_display(wl_display_create())
Compositor::Compositor(MockCompositor *mockCompositor)
: m_mockCompositor(mockCompositor), m_display(wl_display_create())
{
if (wl_display_add_socket(m_display, 0)) {
fprintf(stderr, "Fatal: Failed to open server socket\n");
@ -445,15 +447,19 @@ uint32_t Compositor::nextSerial()
void Compositor::addSurface(Surface *surface)
{
m_mockCompositor->lock();
m_surfaces << surface;
m_mockCompositor->unlock();
}
void Compositor::removeSurface(Surface *surface)
{
m_mockCompositor->lock();
m_surfaces.removeOne(surface);
m_keyboard->handleSurfaceDestroyed(surface);
m_pointer->handleSurfaceDestroyed(surface);
m_fullScreenShellV1->removeSurface(surface);
m_mockCompositor->unlock();
}
Surface *Compositor::resolveSurface(const QVariant &v)

View File

@ -45,6 +45,8 @@
#include <QVector>
#include <QWaitCondition>
class MockCompositor;
namespace Impl {
typedef void (**Implementation)(void);
@ -63,7 +65,7 @@ class XdgShellV6;
class Compositor
{
public:
Compositor();
Compositor(MockCompositor *mockCompositor);
~Compositor();
int fileDescriptor() const { return m_fd; }
@ -114,6 +116,7 @@ private:
void initShm();
MockCompositor *m_mockCompositor = nullptr;
QRect m_outputGeometry;
wl_display *m_display = nullptr;

View File

@ -43,6 +43,7 @@ private slots:
void configureStates();
void popup();
void tooltipOnPopup();
void switchPopups();
void pongs();
void minMaxSize();
void windowGeometry();
@ -332,6 +333,94 @@ void tst_xdgshell::tooltipOnPopup()
QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr);
}
// QTBUG-65680
void tst_xdgshell::switchPopups()
{
class Popup : public QRasterWindow {
public:
explicit Popup(QWindow *parent) {
setTransientParent(parent);
setFlags(Qt::Popup);
resize(10, 10);
show();
}
};
class Window : public QRasterWindow {
public:
void mousePressEvent(QMouseEvent *event) override {
QRasterWindow::mousePressEvent(event);
if (!m_popups.empty())
m_popups.last()->setVisible(false);
}
// The bug this checks for, is the case where there is a flushWindowSystemEvents() call
// somewhere within setVisible(false) before the grab has been released.
// This leads to the the release event below—including its show() call—to be handled
// At a time where there is still an active grab, making it illegal for the new popup to
// grab.
void mouseReleaseEvent(QMouseEvent *event) override {
QRasterWindow::mouseReleaseEvent(event);
m_popups << new Popup(this);
}
~Window() override { qDeleteAll(m_popups); }
QVector<Popup *> m_popups;
};
Window window;
window.resize(200, 200);
window.show();
QCOMPOSITOR_TRY_VERIFY(xdgToplevel());
exec([=] { xdgToplevel()->sendCompleteConfigure(); });
QCOMPOSITOR_TRY_VERIFY(xdgToplevel()->m_xdgSurface->m_committedConfigureSerial);
exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
p->sendEnter(surface, {100, 100});
// p->sendFrame(); //TODO: uncomment when we support seat v5
p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
// p->sendFrame();
p->sendLeave(surface);
// p->sendFrame();
});
QCOMPOSITOR_TRY_VERIFY(xdgPopup());
exec([=] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); });
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial);
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed);
QSignalSpy firstDestroyed(exec([=] { return xdgPopup(); }), &XdgPopup::destroyRequested);
exec([=] {
auto *surface = xdgToplevel()->surface();
auto *p = pointer();
p->sendEnter(surface, {100, 100});
// p->sendFrame();
p->sendButton(client(), BTN_LEFT, Pointer::button_state_pressed);
p->sendButton(client(), BTN_LEFT, Pointer::button_state_released);
// p->sendFrame();
});
// The client will now hide one popup and then show another
firstDestroyed.wait();
QCOMPOSITOR_TRY_VERIFY(xdgPopup());
QCOMPOSITOR_TRY_VERIFY(!xdgPopup(1));
// Verify we still grabbed in case the client has checks to avoid illegal grabs
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_grabbed);
// Sometimes the popup will select another parent if one is illegal at the time it tries to
// grab. So we verify we got the intended parent on the compositor side.
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_parentXdgSurface == xdgToplevel()->m_xdgSurface);
// For good measure just check that configuring works as usual
exec([=] { xdgPopup()->sendCompleteConfigure(QRect(100, 100, 100, 100)); });
QCOMPOSITOR_TRY_VERIFY(xdgPopup()->m_xdgSurface->m_committedConfigureSerial);
}
void tst_xdgshell::pongs()
{
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);