Merge remote-tracking branch 'qt/5.13' into dev
Change-Id: I3dc204fcaa71c01a80b0c622443012eb07964431
This commit is contained in:
commit
dfd4633804
@ -1,5 +1,6 @@
|
||||
{
|
||||
"module": "waylandclient",
|
||||
"condition": "module.gui",
|
||||
"depends": [
|
||||
"gui-private"
|
||||
],
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -98,7 +98,6 @@ public:
|
||||
|
||||
#if QT_CONFIG(cursor)
|
||||
QPlatformCursor *cursor() const override;
|
||||
QWaylandCursor *waylandCursor();
|
||||
#endif
|
||||
|
||||
uint32_t outputId() const { return m_outputId; }
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
|
@ -1,4 +1,9 @@
|
||||
include (../shared/shared.pri)
|
||||
|
||||
qtConfig(cursor) {
|
||||
QMAKE_USE += wayland-cursor
|
||||
QT += gui-private
|
||||
}
|
||||
|
||||
TARGET = tst_seatv4
|
||||
SOURCES += tst_seatv4.cpp
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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?
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user