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",
|
"module": "waylandclient",
|
||||||
|
"condition": "module.gui",
|
||||||
"depends": [
|
"depends": [
|
||||||
"gui-private"
|
"gui-private"
|
||||||
],
|
],
|
||||||
|
@ -380,7 +380,14 @@ void QWaylandXdgSurfaceV6::setGrabPopup(QWaylandWindow *parent, QWaylandInputDev
|
|||||||
auto *top = m_shell->m_topmostGrabbingPopup;
|
auto *top = m_shell->m_topmostGrabbingPopup;
|
||||||
|
|
||||||
if (top && top->m_xdgSurface != parentXdgSurface) {
|
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;
|
parent = top->m_xdgSurface->m_window;
|
||||||
}
|
}
|
||||||
setPopup(parent);
|
setPopup(parent);
|
||||||
|
@ -414,7 +414,14 @@ void QWaylandXdgSurface::setGrabPopup(QWaylandWindow *parent, QWaylandInputDevic
|
|||||||
auto *top = m_shell->m_topmostGrabbingPopup;
|
auto *top = m_shell->m_topmostGrabbingPopup;
|
||||||
|
|
||||||
if (top && top->m_xdgSurface != parentXdgSurface) {
|
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;
|
parent = top->m_xdgSurface->m_window;
|
||||||
}
|
}
|
||||||
setPopup(parent);
|
setPopup(parent);
|
||||||
|
@ -100,14 +100,19 @@ void QWaylandAbstractDecoration::setWaylandWindow(QWaylandWindow *window)
|
|||||||
d->m_wayland_window = 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)
|
static QRegion marginsRegion(const QSize &size, const QMargins &margins)
|
||||||
{
|
{
|
||||||
QRegion r;
|
QRegion r;
|
||||||
r += QRect(0, 0, size.width(), margins.top()); // top
|
const int widthWithMargins = margins.left() + size.width() + margins.right();
|
||||||
r += QRect(0, size.height()+margins.top(), size.width(), margins.bottom()); //bottom
|
r += QRect(0, 0, widthWithMargins, margins.top()); // top
|
||||||
r += QRect(0, 0, margins.left(), size.height()); //left
|
r += QRect(0, size.height()+margins.top(), widthWithMargins, margins.bottom()); //bottom
|
||||||
r += QRect(size.width()+margins.left(), 0, margins.right(), size.height()); // right
|
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;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@
|
|||||||
|
|
||||||
#include "qwaylanddisplay_p.h"
|
#include "qwaylanddisplay_p.h"
|
||||||
#include "qwaylandinputdevice_p.h"
|
#include "qwaylandinputdevice_p.h"
|
||||||
#include "qwaylandscreen_p.h"
|
|
||||||
#include "qwaylandshmbackingstore_p.h"
|
#include "qwaylandshmbackingstore_p.h"
|
||||||
|
|
||||||
#include <QtGui/QImageReader>
|
#include <QtGui/QImageReader>
|
||||||
@ -53,12 +52,6 @@ QT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
namespace QtWaylandClient {
|
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)
|
QWaylandCursorTheme *QWaylandCursorTheme::create(QWaylandShm *shm, int size, const QString &themeName)
|
||||||
{
|
{
|
||||||
QByteArray nameBytes = themeName.toLocal8Bit();
|
QByteArray nameBytes = themeName.toLocal8Bit();
|
||||||
@ -244,56 +237,32 @@ struct wl_cursor_image *QWaylandCursorTheme::cursorImage(Qt::CursorShape shape)
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWaylandCursor::QWaylandCursor(QWaylandScreen *screen)
|
QWaylandCursor::QWaylandCursor(QWaylandDisplay *display)
|
||||||
: mDisplay(screen->display())
|
: mDisplay(display)
|
||||||
, mCursorTheme(mDisplay->loadCursorTheme(screen->devicePixelRatio()))
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<QWaylandBuffer> QWaylandCursor::cursorBitmapImage(const QCursor *cursor)
|
QSharedPointer<QWaylandBuffer> QWaylandCursor::cursorBitmapBuffer(QWaylandDisplay *display, const QCursor *cursor)
|
||||||
{
|
{
|
||||||
if (cursor->shape() != Qt::BitmapCursor)
|
Q_ASSERT(cursor->shape() == Qt::BitmapCursor);
|
||||||
return QSharedPointer<QWaylandShmBuffer>();
|
|
||||||
|
|
||||||
const QImage &img = cursor->pixmap().toImage();
|
const QImage &img = cursor->pixmap().toImage();
|
||||||
QSharedPointer<QWaylandShmBuffer> buffer(new QWaylandShmBuffer(mDisplay, img.size(), img.format()));
|
QSharedPointer<QWaylandShmBuffer> buffer(new QWaylandShmBuffer(display, img.size(), img.format()));
|
||||||
memcpy(buffer->image()->bits(), img.bits(), img.sizeInBytes());
|
memcpy(buffer->image()->bits(), img.bits(), size_t(img.sizeInBytes()));
|
||||||
return buffer;
|
return std::move(buffer);
|
||||||
}
|
|
||||||
|
|
||||||
struct wl_cursor_image *QWaylandCursor::cursorImage(Qt::CursorShape shape)
|
|
||||||
{
|
|
||||||
if (!mCursorTheme)
|
|
||||||
return nullptr;
|
|
||||||
return mCursorTheme->cursorImage(shape);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWaylandCursor::changeCursor(QCursor *cursor, QWindow *window)
|
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) {
|
int fallbackOutputScale = int(window->devicePixelRatio());
|
||||||
mDisplay->setCursor(nullptr, nullptr, 1);
|
const auto seats = mDisplay->inputDevices();
|
||||||
return;
|
for (auto *seat : seats)
|
||||||
}
|
seat->setCursor(cursor, bitmapBuffer, fallbackOutputScale);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWaylandCursor::pointerEvent(const QMouseEvent &event)
|
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";
|
qCWarning(lcQpaWayland) << "Setting cursor position is not possible on wayland";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
} // namespace QtWaylandClient
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -73,7 +73,6 @@ class QWaylandShm;
|
|||||||
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
|
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursorTheme
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static QWaylandCursorTheme *create(QWaylandShm *shm, int size);
|
|
||||||
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
|
static QWaylandCursorTheme *create(QWaylandShm *shm, int size, const QString &themeName);
|
||||||
~QWaylandCursorTheme();
|
~QWaylandCursorTheme();
|
||||||
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
|
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
|
||||||
@ -122,19 +121,18 @@ private:
|
|||||||
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursor : public QPlatformCursor
|
class Q_WAYLAND_CLIENT_EXPORT QWaylandCursor : public QPlatformCursor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QWaylandCursor(QWaylandScreen *screen);
|
explicit QWaylandCursor(QWaylandDisplay *display);
|
||||||
|
|
||||||
void changeCursor(QCursor *cursor, QWindow *window) override;
|
void changeCursor(QCursor *cursor, QWindow *window) override;
|
||||||
void pointerEvent(const QMouseEvent &event) override;
|
void pointerEvent(const QMouseEvent &event) override;
|
||||||
QPoint pos() const override;
|
QPoint pos() const override;
|
||||||
void setPos(const QPoint &pos) 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);
|
struct wl_cursor_image *cursorImage(Qt::CursorShape shape);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWaylandDisplay *mDisplay = nullptr;
|
QWaylandDisplay *mDisplay = nullptr;
|
||||||
QWaylandCursorTheme *mCursorTheme = nullptr;
|
|
||||||
QPoint mLastPos;
|
QPoint mLastPos;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@
|
|||||||
#endif
|
#endif
|
||||||
#if QT_CONFIG(wayland_datadevice)
|
#if QT_CONFIG(wayland_datadevice)
|
||||||
#include "qwaylanddatadevicemanager_p.h"
|
#include "qwaylanddatadevicemanager_p.h"
|
||||||
|
#include "qwaylanddatadevice_p.h"
|
||||||
#endif
|
#endif
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
#include <wayland-cursor.h>
|
#include <wayland-cursor.h>
|
||||||
@ -160,7 +161,7 @@ QWaylandDisplay::~QWaylandDisplay(void)
|
|||||||
delete mDndSelectionHandler.take();
|
delete mDndSelectionHandler.take();
|
||||||
#endif
|
#endif
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
qDeleteAll(mCursorThemesBySize);
|
qDeleteAll(mCursorThemes);
|
||||||
#endif
|
#endif
|
||||||
if (mDisplay)
|
if (mDisplay)
|
||||||
wl_display_disconnect(mDisplay);
|
wl_display_disconnect(mDisplay);
|
||||||
@ -505,40 +506,20 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const
|
|||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#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
|
if (!mCursor)
|
||||||
* for, so set it for all devices. */
|
mCursor.reset(new QWaylandCursor(this));
|
||||||
for (int i = 0; i < mInputDevices.count(); i++) {
|
return mCursor.data();
|
||||||
QWaylandInputDevice *inputDevice = mInputDevices.at(i);
|
|
||||||
inputDevice->setCursor(buffer, image, dpr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
if (auto *theme = mCursorThemes.value({name, pixelSize}, nullptr))
|
||||||
* 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))
|
|
||||||
return theme;
|
return theme;
|
||||||
|
|
||||||
if (auto *theme = QWaylandCursorTheme::create(shm(), cursorSize)) {
|
if (auto *theme = QWaylandCursorTheme::create(shm(), pixelSize, name)) {
|
||||||
mCursorThemesBySize[cursorSize] = theme;
|
mCursorThemes[{name, pixelSize}] = theme;
|
||||||
return theme;
|
return theme;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,6 +528,6 @@ QWaylandCursorTheme *QWaylandDisplay::loadCursorTheme(qreal devicePixelRatio)
|
|||||||
|
|
||||||
#endif // QT_CONFIG(cursor)
|
#endif // QT_CONFIG(cursor)
|
||||||
|
|
||||||
}
|
} // namespace QtWaylandClient
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -93,6 +93,7 @@ class QWaylandWindow;
|
|||||||
class QWaylandIntegration;
|
class QWaylandIntegration;
|
||||||
class QWaylandHardwareIntegration;
|
class QWaylandHardwareIntegration;
|
||||||
class QWaylandShellSurface;
|
class QWaylandShellSurface;
|
||||||
|
class QWaylandCursor;
|
||||||
class QWaylandCursorTheme;
|
class QWaylandCursorTheme;
|
||||||
|
|
||||||
typedef void (*RegistryListener)(void *data,
|
typedef void (*RegistryListener)(void *data,
|
||||||
@ -121,9 +122,8 @@ public:
|
|||||||
|
|
||||||
QWaylandWindowManagerIntegration *windowManagerIntegration() const;
|
QWaylandWindowManagerIntegration *windowManagerIntegration() const;
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
void setCursor(struct wl_buffer *buffer, struct wl_cursor_image *image, qreal dpr);
|
QWaylandCursor *waylandCursor();
|
||||||
void setCursor(const QSharedPointer<QWaylandBuffer> &buffer, const QPoint &hotSpot, qreal dpr);
|
QWaylandCursorTheme *loadCursorTheme(const QString &name, int pixelSize);
|
||||||
QWaylandCursorTheme *loadCursorTheme(qreal devicePixelRatio);
|
|
||||||
#endif
|
#endif
|
||||||
struct wl_display *wl_display() const { return mDisplay; }
|
struct wl_display *wl_display() const { return mDisplay; }
|
||||||
struct ::wl_registry *wl_registry() { return object(); }
|
struct ::wl_registry *wl_registry() { return object(); }
|
||||||
@ -210,7 +210,8 @@ private:
|
|||||||
QList<Listener> mRegistryListeners;
|
QList<Listener> mRegistryListeners;
|
||||||
QWaylandIntegration *mWaylandIntegration = nullptr;
|
QWaylandIntegration *mWaylandIntegration = nullptr;
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
QMap<int, QWaylandCursorTheme *> mCursorThemesBySize;
|
QMap<std::pair<QString, int>, QWaylandCursorTheme *> mCursorThemes; // theme name and size
|
||||||
|
QScopedPointer<QWaylandCursor> mCursor;
|
||||||
#endif
|
#endif
|
||||||
#if QT_CONFIG(wayland_datadevice)
|
#if QT_CONFIG(wayland_datadevice)
|
||||||
QScopedPointer<QWaylandDataDeviceManager> mDndSelectionHandler;
|
QScopedPointer<QWaylandDataDeviceManager> mDndSelectionHandler;
|
||||||
|
@ -170,13 +170,13 @@ QWaylandInputDevice::Keyboard::~Keyboard()
|
|||||||
wl_keyboard_destroy(object());
|
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)
|
QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat)
|
||||||
: mParent(p)
|
: mParent(seat)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,6 +188,191 @@ QWaylandInputDevice::Pointer::~Pointer()
|
|||||||
wl_pointer_destroy(object());
|
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)
|
QWaylandInputDevice::Touch::Touch(QWaylandInputDevice *p)
|
||||||
: mParent(p)
|
: mParent(p)
|
||||||
{
|
{
|
||||||
@ -276,12 +461,6 @@ QWaylandInputDevice::Touch *QWaylandInputDevice::createTouch(QWaylandInputDevice
|
|||||||
return new Touch(device);
|
return new Touch(device);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QWaylandInputDevice::handleWindowDestroyed(QWaylandWindow *window)
|
|
||||||
{
|
|
||||||
if (mKeyboard && window == mKeyboard->mFocus)
|
|
||||||
mKeyboard->stopRepeat();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QWaylandInputDevice::handleEndDrag()
|
void QWaylandInputDevice::handleEndDrag()
|
||||||
{
|
{
|
||||||
if (mTouch)
|
if (mTouch)
|
||||||
@ -325,7 +504,7 @@ QWaylandWindow *QWaylandInputDevice::pointerFocus() const
|
|||||||
|
|
||||||
QWaylandWindow *QWaylandInputDevice::keyboardFocus() const
|
QWaylandWindow *QWaylandInputDevice::keyboardFocus() const
|
||||||
{
|
{
|
||||||
return mKeyboard ? mKeyboard->mFocus : nullptr;
|
return mKeyboard ? mKeyboard->focusWindow() : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QWaylandWindow *QWaylandInputDevice::touchFocus() const
|
QWaylandWindow *QWaylandInputDevice::touchFocus() const
|
||||||
@ -361,87 +540,33 @@ Qt::KeyboardModifiers QWaylandInputDevice::Keyboard::modifiers() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#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)
|
if (mPointer)
|
||||||
return mPointer->mCursorSerial;
|
mPointer->updateCursor();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -461,7 +586,16 @@ void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surf
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
|
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;
|
mFocus = window;
|
||||||
|
connect(mFocus, &QWaylandWindow::wlSurfaceDestroyed, this, &Pointer::handleFocusDestroyed);
|
||||||
|
|
||||||
mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy));
|
mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy));
|
||||||
mGlobalPos = window->window()->mapToGlobal(mSurfacePos.toPoint());
|
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)
|
#if QT_CONFIG(cursor)
|
||||||
// Depends on mEnterSerial being updated
|
// Depends on mEnterSerial being updated
|
||||||
window->window()->setCursor(window->window()->cursor());
|
updateCursor();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QWaylandWindow *grab = QWaylandWindow::mouseGrab();
|
QWaylandWindow *grab = QWaylandWindow::mouseGrab();
|
||||||
@ -498,7 +632,8 @@ void QWaylandInputDevice::Pointer::pointer_leave(uint32_t time, struct wl_surfac
|
|||||||
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
|
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
|
||||||
setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos));
|
setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos));
|
||||||
}
|
}
|
||||||
mFocus = nullptr;
|
|
||||||
|
invalidateFocus();
|
||||||
mButtons = Qt::NoButton;
|
mButtons = Qt::NoButton;
|
||||||
|
|
||||||
mParent->mTime = time;
|
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()));
|
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()
|
void QWaylandInputDevice::Pointer::releaseButtons()
|
||||||
{
|
{
|
||||||
mButtons = Qt::NoButton;
|
mButtons = Qt::NoButton;
|
||||||
@ -932,12 +1074,19 @@ void QWaylandInputDevice::Keyboard::keyboard_enter(uint32_t time, struct wl_surf
|
|||||||
Q_UNUSED(time);
|
Q_UNUSED(time);
|
||||||
Q_UNUSED(keys);
|
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;
|
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 = surface;
|
||||||
mFocus = window;
|
connect(focusWindow(), &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
|
||||||
|
|
||||||
mParent->mQDisplay->handleKeyboardFocusChanged(mParent);
|
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)
|
void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t time, struct wl_surface *surface)
|
||||||
{
|
{
|
||||||
Q_UNUSED(time);
|
Q_UNUSED(time);
|
||||||
Q_UNUSED(surface);
|
|
||||||
|
|
||||||
if (surface) {
|
if (!surface) {
|
||||||
QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface);
|
// Either a compositor bug, or a race condition with wl_surface.destroy, ignore the event.
|
||||||
window->unfocus();
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mFocus = nullptr;
|
if (surface != mFocus) {
|
||||||
|
qCWarning(lcQpaWayland) << "Ignoring unexpected wl_keyboard.leave event."
|
||||||
mParent->mQDisplay->handleKeyboardFocusChanged(mParent);
|
<< "wl_surface argument does not match the current focus"
|
||||||
|
<< "This is most likely a compositor bug";
|
||||||
mRepeatTimer.stop();
|
return;
|
||||||
|
}
|
||||||
|
disconnect(focusWindow(), &QWaylandWindow::wlSurfaceDestroyed, this, &Keyboard::handleFocusDestroyed);
|
||||||
|
handleFocusLost();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void sendKey(QWindow *tlw, ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers,
|
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)
|
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) {
|
if (!window) {
|
||||||
// We destroyed the keyboard focus surface, but the server didn't get the message yet...
|
// 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.
|
// 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()
|
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...
|
// 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.
|
// or the server didn't send an enter event first.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mRepeatTimer.setInterval(mRepeatRate);
|
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)
|
#if QT_CONFIG(xkbcommon)
|
||||||
mRepeatSym, mNativeModifiers,
|
mRepeatSym, mNativeModifiers,
|
||||||
#else
|
#else
|
||||||
@ -1076,7 +1228,7 @@ void QWaylandInputDevice::Keyboard::repeatKey()
|
|||||||
#endif
|
#endif
|
||||||
mRepeatText, true);
|
mRepeatText, true);
|
||||||
|
|
||||||
sendKey(mFocus->window(), mRepeatTime, QEvent::KeyPress, mRepeatKey, modifiers(), mRepeatCode,
|
sendKey(window->window(), mRepeatTime, QEvent::KeyPress, mRepeatKey, modifiers(), mRepeatCode,
|
||||||
#if QT_CONFIG(xkbcommon)
|
#if QT_CONFIG(xkbcommon)
|
||||||
mRepeatSym, mNativeModifiers,
|
mRepeatSym, mNativeModifiers,
|
||||||
#else
|
#else
|
||||||
@ -1085,6 +1237,27 @@ void QWaylandInputDevice::Keyboard::repeatKey()
|
|||||||
mRepeatText, true);
|
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,
|
void QWaylandInputDevice::Keyboard::keyboard_modifiers(uint32_t serial,
|
||||||
uint32_t mods_depressed,
|
uint32_t mods_depressed,
|
||||||
uint32_t mods_latched,
|
uint32_t mods_latched,
|
||||||
@ -1186,7 +1359,7 @@ void QWaylandInputDevice::handleTouchPoint(int id, double x, double y, Qt::Touch
|
|||||||
if (!win && mPointer)
|
if (!win && mPointer)
|
||||||
win = mPointer->mFocus;
|
win = mPointer->mFocus;
|
||||||
if (!win && mKeyboard)
|
if (!win && mKeyboard)
|
||||||
win = mKeyboard->mFocus;
|
win = mKeyboard->focusWindow();
|
||||||
if (!win || !win->window())
|
if (!win || !win->window())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -88,6 +88,10 @@ class QWaylandWindow;
|
|||||||
class QWaylandDisplay;
|
class QWaylandDisplay;
|
||||||
class QWaylandDataDevice;
|
class QWaylandDataDevice;
|
||||||
class QWaylandTextInput;
|
class QWaylandTextInput;
|
||||||
|
#if QT_CONFIG(cursor)
|
||||||
|
class QWaylandCursorTheme;
|
||||||
|
class CursorSurface;
|
||||||
|
#endif
|
||||||
|
|
||||||
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice
|
class Q_WAYLAND_CLIENT_EXPORT QWaylandInputDevice
|
||||||
: public QObject
|
: public QObject
|
||||||
@ -107,12 +111,8 @@ public:
|
|||||||
struct ::wl_seat *wl_seat() { return QtWayland::wl_seat::object(); }
|
struct ::wl_seat *wl_seat() { return QtWayland::wl_seat::object(); }
|
||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
void setCursor(const QCursor &cursor, QWaylandScreen *screen);
|
void setCursor(const QCursor *cursor, const QSharedPointer<QWaylandBuffer> &cachedBuffer = {}, int fallbackOutputScale = 1);
|
||||||
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);
|
|
||||||
#endif
|
#endif
|
||||||
void handleWindowDestroyed(QWaylandWindow *window);
|
|
||||||
void handleEndDrag();
|
void handleEndDrag();
|
||||||
|
|
||||||
#if QT_CONFIG(wayland_datadevice)
|
#if QT_CONFIG(wayland_datadevice)
|
||||||
@ -134,22 +134,27 @@ public:
|
|||||||
Qt::KeyboardModifiers modifiers() const;
|
Qt::KeyboardModifiers modifiers() const;
|
||||||
|
|
||||||
uint32_t serial() const;
|
uint32_t serial() const;
|
||||||
uint32_t cursorSerial() const;
|
|
||||||
|
|
||||||
virtual Keyboard *createKeyboard(QWaylandInputDevice *device);
|
virtual Keyboard *createKeyboard(QWaylandInputDevice *device);
|
||||||
virtual Pointer *createPointer(QWaylandInputDevice *device);
|
virtual Pointer *createPointer(QWaylandInputDevice *device);
|
||||||
virtual Touch *createTouch(QWaylandInputDevice *device);
|
virtual Touch *createTouch(QWaylandInputDevice *device);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void setCursor(Qt::CursorShape cursor, QWaylandScreen *screen);
|
|
||||||
|
|
||||||
QWaylandDisplay *mQDisplay = nullptr;
|
QWaylandDisplay *mQDisplay = nullptr;
|
||||||
struct wl_display *mDisplay = nullptr;
|
struct wl_display *mDisplay = nullptr;
|
||||||
|
|
||||||
int mVersion;
|
int mVersion;
|
||||||
uint32_t mCaps = 0;
|
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)
|
#if QT_CONFIG(wayland_datadevice)
|
||||||
QWaylandDataDevice *mDataDevice = nullptr;
|
QWaylandDataDevice *mDataDevice = nullptr;
|
||||||
@ -169,8 +174,6 @@ private:
|
|||||||
|
|
||||||
QTouchDevice *mTouchDevice = nullptr;
|
QTouchDevice *mTouchDevice = nullptr;
|
||||||
|
|
||||||
QSharedPointer<QWaylandBuffer> mPixmapCursor;
|
|
||||||
|
|
||||||
friend class QWaylandTouchExtension;
|
friend class QWaylandTouchExtension;
|
||||||
friend class QWaylandQtKeyExtension;
|
friend class QWaylandQtKeyExtension;
|
||||||
};
|
};
|
||||||
@ -189,7 +192,7 @@ public:
|
|||||||
Keyboard(QWaylandInputDevice *p);
|
Keyboard(QWaylandInputDevice *p);
|
||||||
~Keyboard() override;
|
~Keyboard() override;
|
||||||
|
|
||||||
void stopRepeat();
|
QWaylandWindow *focusWindow() const;
|
||||||
|
|
||||||
void keyboard_keymap(uint32_t format,
|
void keyboard_keymap(uint32_t format,
|
||||||
int32_t fd,
|
int32_t fd,
|
||||||
@ -209,7 +212,7 @@ public:
|
|||||||
void keyboard_repeat_info(int32_t rate, int32_t delay) override;
|
void keyboard_repeat_info(int32_t rate, int32_t delay) override;
|
||||||
|
|
||||||
QWaylandInputDevice *mParent = nullptr;
|
QWaylandInputDevice *mParent = nullptr;
|
||||||
QPointer<QWaylandWindow> mFocus;
|
::wl_surface *mFocus = nullptr;
|
||||||
#if QT_CONFIG(xkbcommon)
|
#if QT_CONFIG(xkbcommon)
|
||||||
xkb_context *mXkbContext = nullptr;
|
xkb_context *mXkbContext = nullptr;
|
||||||
xkb_keymap *mXkbMap = nullptr;
|
xkb_keymap *mXkbMap = nullptr;
|
||||||
@ -234,6 +237,8 @@ public:
|
|||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void repeatKey();
|
void repeatKey();
|
||||||
|
void handleFocusDestroyed();
|
||||||
|
void handleFocusLost();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#if QT_CONFIG(xkbcommon)
|
#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:
|
public:
|
||||||
Pointer(QWaylandInputDevice *p);
|
explicit Pointer(QWaylandInputDevice *seat);
|
||||||
~Pointer() override;
|
~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,
|
void pointer_enter(uint32_t serial, struct wl_surface *surface,
|
||||||
wl_fixed_t sx, wl_fixed_t sy) override;
|
wl_fixed_t sx, wl_fixed_t sy) override;
|
||||||
void pointer_leave(uint32_t time, struct wl_surface *surface) 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_axis_discrete(uint32_t axis, int32_t value) override;
|
||||||
void pointer_frame() override;
|
void pointer_frame() override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void handleFocusDestroyed() { invalidateFocus(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void invalidateFocus();
|
||||||
|
|
||||||
|
public:
|
||||||
void releaseButtons();
|
void releaseButtons();
|
||||||
|
|
||||||
QWaylandInputDevice *mParent = nullptr;
|
QWaylandInputDevice *mParent = nullptr;
|
||||||
QPointer<QWaylandWindow> mFocus;
|
QPointer<QWaylandWindow> mFocus;
|
||||||
uint32_t mEnterSerial = 0;
|
uint32_t mEnterSerial = 0;
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
uint32_t mCursorSerial = 0;
|
struct {
|
||||||
|
QWaylandCursorTheme *theme = nullptr;
|
||||||
|
int themeBufferScale = 0;
|
||||||
|
QScopedPointer<CursorSurface> surface;
|
||||||
|
} mCursor;
|
||||||
#endif
|
#endif
|
||||||
QPointF mSurfacePos;
|
QPointF mSurfacePos;
|
||||||
QPointF mGlobalPos;
|
QPointF mGlobalPos;
|
||||||
|
@ -176,19 +176,10 @@ qreal QWaylandScreen::refreshRate() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
|
|
||||||
QPlatformCursor *QWaylandScreen::cursor() const
|
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)
|
#endif // QT_CONFIG(cursor)
|
||||||
|
|
||||||
QWaylandScreen * QWaylandScreen::waylandScreenFromWindow(QWindow *window)
|
QWaylandScreen * QWaylandScreen::waylandScreenFromWindow(QWindow *window)
|
||||||
|
@ -98,7 +98,6 @@ public:
|
|||||||
|
|
||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
QPlatformCursor *cursor() const override;
|
QPlatformCursor *cursor() const override;
|
||||||
QWaylandCursor *waylandCursor();
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t outputId() const { return m_outputId; }
|
uint32_t outputId() const { return m_outputId; }
|
||||||
|
@ -51,11 +51,6 @@
|
|||||||
#include "qwaylanddecorationfactory_p.h"
|
#include "qwaylanddecorationfactory_p.h"
|
||||||
#include "qwaylandshmbackingstore_p.h"
|
#include "qwaylandshmbackingstore_p.h"
|
||||||
|
|
||||||
#if QT_CONFIG(wayland_datadevice)
|
|
||||||
#include "qwaylanddatadevice_p.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#include <QtCore/QFileInfo>
|
#include <QtCore/QFileInfo>
|
||||||
#include <QtCore/QPointer>
|
#include <QtCore/QPointer>
|
||||||
#include <QtCore/QRegularExpression>
|
#include <QtCore/QRegularExpression>
|
||||||
@ -95,10 +90,6 @@ QWaylandWindow::~QWaylandWindow()
|
|||||||
if (isInitialized())
|
if (isInitialized())
|
||||||
reset(false);
|
reset(false);
|
||||||
|
|
||||||
QList<QWaylandInputDevice *> inputDevices = mDisplay->inputDevices();
|
|
||||||
for (int i = 0; i < inputDevices.size(); ++i)
|
|
||||||
inputDevices.at(i)->handleWindowDestroyed(this);
|
|
||||||
|
|
||||||
const QWindow *parent = window();
|
const QWindow *parent = window();
|
||||||
foreach (QWindow *w, QGuiApplication::topLevelWindows()) {
|
foreach (QWindow *w, QGuiApplication::topLevelWindows()) {
|
||||||
if (w->transientParent() == parent)
|
if (w->transientParent() == parent)
|
||||||
@ -236,8 +227,10 @@ void QWaylandWindow::reset(bool sendDestroyEvent)
|
|||||||
mShellSurface = nullptr;
|
mShellSurface = nullptr;
|
||||||
delete mSubSurfaceWindow;
|
delete mSubSurfaceWindow;
|
||||||
mSubSurfaceWindow = nullptr;
|
mSubSurfaceWindow = nullptr;
|
||||||
if (isInitialized())
|
if (isInitialized()) {
|
||||||
|
emit wlSurfaceDestroyed();
|
||||||
destroy();
|
destroy();
|
||||||
|
}
|
||||||
mScreens.clear();
|
mScreens.clear();
|
||||||
|
|
||||||
if (mFrameCallback) {
|
if (mFrameCallback) {
|
||||||
@ -396,14 +389,9 @@ void QWaylandWindow::setVisible(bool visible)
|
|||||||
// QWaylandShmBackingStore::beginPaint().
|
// QWaylandShmBackingStore::beginPaint().
|
||||||
} else {
|
} else {
|
||||||
sendExposeEvent(QRect());
|
sendExposeEvent(QRect());
|
||||||
// when flushing the event queue, it could contain a close event, in which
|
if (window()->type() == Qt::Popup)
|
||||||
// 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)
|
|
||||||
closePopups(this);
|
closePopups(this);
|
||||||
if (!deleteGuard.isNull())
|
reset();
|
||||||
reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -970,7 +958,8 @@ void QWaylandWindow::handleScreenChanged()
|
|||||||
#if QT_CONFIG(cursor)
|
#if QT_CONFIG(cursor)
|
||||||
void QWaylandWindow::setMouseCursor(QWaylandInputDevice *device, const QCursor &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)
|
void QWaylandWindow::restoreMouseCursor(QWaylandInputDevice *device)
|
||||||
@ -984,16 +973,6 @@ void QWaylandWindow::requestActivateWindow()
|
|||||||
qCWarning(lcQpaWayland) << "Wayland does not support QWindow::requestActivate()";
|
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
|
bool QWaylandWindow::isExposed() const
|
||||||
{
|
{
|
||||||
if (mShellSurface)
|
if (mShellSurface)
|
||||||
|
@ -154,7 +154,6 @@ public:
|
|||||||
void requestActivateWindow() override;
|
void requestActivateWindow() override;
|
||||||
bool isExposed() const override;
|
bool isExposed() const override;
|
||||||
bool isActive() const override;
|
bool isActive() const override;
|
||||||
void unfocus();
|
|
||||||
|
|
||||||
QWaylandAbstractDecoration *decoration() const;
|
QWaylandAbstractDecoration *decoration() const;
|
||||||
|
|
||||||
@ -200,6 +199,9 @@ public:
|
|||||||
public slots:
|
public slots:
|
||||||
void applyConfigure();
|
void applyConfigure();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void wlSurfaceDestroyed();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void surface_enter(struct ::wl_output *output) override;
|
void surface_enter(struct ::wl_output *output) override;
|
||||||
void surface_leave(struct ::wl_output *output) override;
|
void surface_leave(struct ::wl_output *output) override;
|
||||||
|
@ -47,7 +47,7 @@ public:
|
|||||||
add<DataDeviceManager>(dataDeviceVersion);
|
add<DataDeviceManager>(dataDeviceVersion);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DataDevice *dataDevice() { return get<Seat>()->dataDevice(); }
|
DataDevice *dataDevice() { return get<DataDeviceManager>()->deviceFor(get<Seat>()); }
|
||||||
};
|
};
|
||||||
|
|
||||||
class tst_datadevicev1 : public QObject, private DataDeviceCompositor
|
class tst_datadevicev1 : public QObject, private DataDeviceCompositor
|
||||||
@ -58,6 +58,8 @@ private slots:
|
|||||||
void initTestCase();
|
void initTestCase();
|
||||||
void pasteAscii();
|
void pasteAscii();
|
||||||
void pasteUtf8();
|
void pasteUtf8();
|
||||||
|
void destroysPreviousSelection();
|
||||||
|
void destroysSelectionWithSurface();
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_datadevicev1::initTestCase()
|
void tst_datadevicev1::initTestCase()
|
||||||
@ -69,7 +71,8 @@ void tst_datadevicev1::initTestCase()
|
|||||||
QCOMPOSITOR_TRY_VERIFY(keyboard());
|
QCOMPOSITOR_TRY_VERIFY(keyboard());
|
||||||
|
|
||||||
QCOMPOSITOR_TRY_VERIFY(dataDevice());
|
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()
|
void tst_datadevicev1::pasteAscii()
|
||||||
@ -86,7 +89,8 @@ void tst_datadevicev1::pasteAscii()
|
|||||||
|
|
||||||
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
exec([&] {
|
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) {
|
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
|
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
|
||||||
@ -94,16 +98,14 @@ void tst_datadevicev1::pasteAscii()
|
|||||||
file.write(QByteArray("normal ascii"));
|
file.write(QByteArray("normal ascii"));
|
||||||
file.close();
|
file.close();
|
||||||
});
|
});
|
||||||
dataDevice()->sendDataOffer(offer);
|
|
||||||
offer->send_offer("text/plain");
|
|
||||||
dataDevice()->sendSelection(offer);
|
dataDevice()->sendSelection(offer);
|
||||||
|
|
||||||
auto *surface = xdgSurface()->m_surface;
|
auto *surface = xdgSurface()->m_surface;
|
||||||
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
|
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
|
||||||
|
|
||||||
pointer()->sendEnter(surface, {32, 32});
|
pointer()->sendEnter(surface, {32, 32});
|
||||||
pointer()->sendButton(client(), BTN_LEFT, 1);
|
pointer()->sendButton(client, BTN_LEFT, 1);
|
||||||
pointer()->sendButton(client(), BTN_LEFT, 0);
|
pointer()->sendButton(client, BTN_LEFT, 0);
|
||||||
});
|
});
|
||||||
QTRY_COMPARE(window.m_text, "normal ascii");
|
QTRY_COMPARE(window.m_text, "normal ascii");
|
||||||
}
|
}
|
||||||
@ -122,7 +124,8 @@ void tst_datadevicev1::pasteUtf8()
|
|||||||
|
|
||||||
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
exec([&] {
|
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) {
|
connect(offer, &DataOffer::receive, [](QString mimeType, int fd) {
|
||||||
QFile file;
|
QFile file;
|
||||||
file.open(fd, QIODevice::WriteOnly, QFile::FileHandleFlag::AutoCloseHandle);
|
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.write(QByteArray("face with tears of joy: 😂"));
|
||||||
file.close();
|
file.close();
|
||||||
});
|
});
|
||||||
dataDevice()->sendDataOffer(offer);
|
|
||||||
offer->send_offer("text/plain");
|
|
||||||
offer->send_offer("text/plain;charset=utf-8");
|
|
||||||
dataDevice()->sendSelection(offer);
|
dataDevice()->sendSelection(offer);
|
||||||
|
|
||||||
auto *surface = xdgSurface()->m_surface;
|
auto *surface = xdgSurface()->m_surface;
|
||||||
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
|
keyboard()->sendEnter(surface); // Need to set keyboard focus according to protocol
|
||||||
|
|
||||||
pointer()->sendEnter(surface, {32, 32});
|
pointer()->sendEnter(surface, {32, 32});
|
||||||
pointer()->sendButton(client(), BTN_LEFT, 1);
|
pointer()->sendButton(client, BTN_LEFT, 1);
|
||||||
pointer()->sendButton(client(), BTN_LEFT, 0);
|
pointer()->sendButton(client, BTN_LEFT, 0);
|
||||||
});
|
});
|
||||||
QTRY_COMPARE(window.m_text, "face with tears of joy: 😂");
|
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)
|
QCOMPOSITOR_TEST_MAIN(tst_datadevicev1)
|
||||||
#include "tst_datadevicev1.moc"
|
#include "tst_datadevicev1.moc"
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
include (../shared/shared.pri)
|
include (../shared/shared.pri)
|
||||||
|
|
||||||
|
qtConfig(cursor) {
|
||||||
|
QMAKE_USE += wayland-cursor
|
||||||
|
QT += gui-private
|
||||||
|
}
|
||||||
|
|
||||||
TARGET = tst_seatv4
|
TARGET = tst_seatv4
|
||||||
SOURCES += tst_seatv4.cpp
|
SOURCES += tst_seatv4.cpp
|
||||||
|
@ -30,6 +30,12 @@
|
|||||||
|
|
||||||
#include <QtGui/QRasterWindow>
|
#include <QtGui/QRasterWindow>
|
||||||
#include <QtGui/QOpenGLWindow>
|
#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;
|
using namespace MockCompositor;
|
||||||
|
|
||||||
@ -59,16 +65,22 @@ class tst_seatv4 : public QObject, private SeatV4Compositor
|
|||||||
private slots:
|
private slots:
|
||||||
void cleanup();
|
void cleanup();
|
||||||
void bindsToSeat();
|
void bindsToSeat();
|
||||||
|
void keyboardKeyPress();
|
||||||
|
#if QT_CONFIG(cursor)
|
||||||
void createsPointer();
|
void createsPointer();
|
||||||
void setsCursorOnEnter();
|
void setsCursorOnEnter();
|
||||||
void usesEnterSerial();
|
void usesEnterSerial();
|
||||||
|
void focusDestruction();
|
||||||
void mousePress();
|
void mousePress();
|
||||||
void simpleAxis_data();
|
void simpleAxis_data();
|
||||||
void simpleAxis();
|
void simpleAxis();
|
||||||
void invalidPointerEvents();
|
void invalidPointerEvents();
|
||||||
void scaledCursor();
|
void scaledCursor();
|
||||||
|
void unscaledFallbackCursor();
|
||||||
void keyboardKeyPress();
|
void bitmapCursor();
|
||||||
|
void hidpiBitmapCursor();
|
||||||
|
void hidpiBitmapCursorNonInt();
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_seatv4::cleanup()
|
void tst_seatv4::cleanup()
|
||||||
@ -83,6 +95,31 @@ void tst_seatv4::bindsToSeat()
|
|||||||
QCOMPOSITOR_COMPARE(get<Seat>()->resourceMap().first()->version(), 4);
|
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()
|
void tst_seatv4::createsPointer()
|
||||||
{
|
{
|
||||||
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
|
QCOMPOSITOR_TRY_COMPARE(pointer()->resourceMap().size(), 1);
|
||||||
@ -97,7 +134,7 @@ void tst_seatv4::setsCursorOnEnter()
|
|||||||
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
|
|
||||||
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
||||||
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_seatv4::usesEnterSerial()
|
void tst_seatv4::usesEnterSerial()
|
||||||
@ -111,12 +148,43 @@ void tst_seatv4::usesEnterSerial()
|
|||||||
uint enterSerial = exec([=] {
|
uint enterSerial = exec([=] {
|
||||||
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
|
return pointer()->sendEnter(xdgSurface()->m_surface, {32, 32});
|
||||||
});
|
});
|
||||||
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
|
||||||
|
|
||||||
QTRY_COMPARE(setCursorSpy.count(), 1);
|
QTRY_COMPARE(setCursorSpy.count(), 1);
|
||||||
QCOMPARE(setCursorSpy.takeFirst().at(0).toUInt(), enterSerial);
|
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()
|
void tst_seatv4::mousePress()
|
||||||
{
|
{
|
||||||
class Window : public QRasterWindow {
|
class Window : public QRasterWindow {
|
||||||
@ -253,9 +321,42 @@ void tst_seatv4::invalidPointerEvents()
|
|||||||
xdgPingAndWaitForPong();
|
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()
|
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
|
// Add a highdpi output
|
||||||
exec([&] {
|
exec([&] {
|
||||||
OutputData d;
|
OutputData d;
|
||||||
@ -270,47 +371,190 @@ void tst_seatv4::scaledCursor()
|
|||||||
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
|
|
||||||
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
||||||
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface());
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
|
||||||
QCOMPOSITOR_TRY_VERIFY(pointer()->cursorSurface()->m_committed.buffer);
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
|
||||||
QCOMPOSITOR_TRY_COMPARE(pointer()->cursorSurface()->m_committed.bufferScale, 1);
|
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
|
||||||
QSize unscaledPixelSize = exec([=] {
|
QSize unscaledPixelSize = exec([=] {
|
||||||
return pointer()->cursorSurface()->m_committed.buffer->size();
|
return cursorSurface()->m_committed.buffer->size();
|
||||||
});
|
});
|
||||||
|
|
||||||
exec([=] {
|
exec([=] {
|
||||||
auto *surface = pointer()->cursorSurface();
|
auto *surface = cursorSurface();
|
||||||
surface->sendEnter(getAll<Output>()[1]);
|
surface->sendEnter(getAll<Output>()[1]);
|
||||||
surface->sendLeave(getAll<Output>()[0]);
|
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
|
// Remove the extra output to clean up for the next test
|
||||||
exec([&] { remove(output(1)); });
|
exec([&] { remove(output(1)); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_seatv4::keyboardKeyPress()
|
void tst_seatv4::unscaledFallbackCursor()
|
||||||
{
|
{
|
||||||
class Window : public QRasterWindow {
|
const uint defaultSize = defaultCursorSize();
|
||||||
public:
|
if (!supportsCursorSizes({defaultSize}))
|
||||||
void keyPressEvent(QKeyEvent *) override { m_pressed = true; }
|
QSKIP("Default cursor size not supported");
|
||||||
bool m_pressed = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
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.resize(64, 64);
|
||||||
window.show();
|
window.show();
|
||||||
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
QCOMPOSITOR_TRY_VERIFY(xdgSurface() && xdgSurface()->m_committedConfigureSerial);
|
||||||
|
exec([=] { pointer()->sendEnter(xdgSurface()->m_surface, {32, 32}); });
|
||||||
uint keyCode = 80; // arbitrarily chosen
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface());
|
||||||
exec([&] {
|
QCOMPOSITOR_TRY_VERIFY(cursorSurface()->m_committed.buffer);
|
||||||
auto *surface = xdgSurface()->m_surface;
|
QCOMPOSITOR_TRY_COMPARE(cursorSurface()->m_committed.bufferScale, 1);
|
||||||
keyboard()->sendEnter(surface);
|
QSize unscaledPixelSize = exec([=] {
|
||||||
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_pressed);
|
return cursorSurface()->m_committed.buffer->size();
|
||||||
keyboard()->sendKey(client(), keyCode, Keyboard::key_state_released);
|
|
||||||
});
|
});
|
||||||
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)
|
QCOMPOSITOR_TEST_MAIN(tst_seatv4)
|
||||||
#include "tst_seatv4.moc"
|
#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 x = wl_fixed_from_double(position.x());
|
||||||
wl_fixed_t y = wl_fixed_from_double(position.y());
|
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();
|
wl_client *client = surface->resource()->client();
|
||||||
const auto pointerResources = resourceMap().values(client);
|
const auto pointerResources = resourceMap().values(client);
|
||||||
for (auto *r : pointerResources)
|
for (auto *r : pointerResources)
|
||||||
wl_pointer::send_enter(r->handle, m_enterSerial, surface->resource()->handle, x ,y);
|
wl_pointer::send_enter(r->handle, serial, surface->resource()->handle, x ,y);
|
||||||
return m_enterSerial;
|
return serial;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint Pointer::sendLeave(Surface *surface)
|
uint Pointer::sendLeave(Surface *surface)
|
||||||
{
|
{
|
||||||
m_enterSerial = 0;
|
|
||||||
uint serial = m_seat->m_compositor->nextSerial();
|
uint serial = m_seat->m_compositor->nextSerial();
|
||||||
|
|
||||||
wl_client *client = surface->resource()->client();
|
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)
|
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(resource);
|
||||||
Q_UNUSED(hotspot_x);
|
|
||||||
Q_UNUSED(hotspot_y);
|
|
||||||
auto *s = fromResource<Surface>(surface);
|
auto *s = fromResource<Surface>(surface);
|
||||||
QVERIFY(s);
|
QVERIFY(s);
|
||||||
|
|
||||||
if (s->m_role) {
|
if (s->m_role) {
|
||||||
auto *cursorRole = CursorRole::fromSurface(s);
|
m_cursorRole = CursorRole::fromSurface(s);
|
||||||
QVERIFY(cursorRole);
|
QVERIFY(m_cursorRole);
|
||||||
QVERIFY(cursorRole == m_cursorRole);
|
|
||||||
} else {
|
} else {
|
||||||
m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
|
m_cursorRole = new CursorRole(s); //TODO: make sure we don't leak CursorRole
|
||||||
s->m_role = m_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);
|
emit setCursor(serial);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,9 +250,6 @@ public:
|
|||||||
Keyboard* m_keyboard = nullptr;
|
Keyboard* m_keyboard = nullptr;
|
||||||
QVector<Keyboard *> m_oldKeyboards;
|
QVector<Keyboard *> m_oldKeyboards;
|
||||||
|
|
||||||
DataDevice *dataDevice() { return m_dataDevice.data(); }
|
|
||||||
QScopedPointer<DataDevice> m_dataDevice;
|
|
||||||
|
|
||||||
uint m_capabilities = 0;
|
uint m_capabilities = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@ -288,7 +285,8 @@ public:
|
|||||||
void sendFrame(wl_client *client);
|
void sendFrame(wl_client *client);
|
||||||
|
|
||||||
Seat *m_seat = nullptr;
|
Seat *m_seat = nullptr;
|
||||||
uint m_enterSerial = 0;
|
QVector<uint> m_enterSerials;
|
||||||
|
QPoint m_hotspot;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void setCursor(uint serial); //TODO: add arguments?
|
void setCursor(uint serial); //TODO: add arguments?
|
||||||
|
@ -30,29 +30,60 @@
|
|||||||
|
|
||||||
namespace MockCompositor {
|
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)
|
void DataDeviceManager::data_device_manager_get_data_device(Resource *resource, uint32_t id, wl_resource *seatResource)
|
||||||
{
|
{
|
||||||
auto *seat = fromResource<Seat>(seatResource);
|
auto *seat = fromResource<Seat>(seatResource);
|
||||||
QVERIFY(seat);
|
QVERIFY(seat);
|
||||||
QVERIFY(!seat->m_dataDevice);
|
auto *device = deviceFor(seat);
|
||||||
seat->m_dataDevice.reset(new DataDevice(resource->client(), id, resource->version()));
|
device->add(resource->client(), id, resource->version());
|
||||||
}
|
}
|
||||||
|
|
||||||
DataDevice::~DataDevice()
|
DataDevice::~DataDevice()
|
||||||
{
|
{
|
||||||
// If the client hasn't deleted the wayland object, just ignore subsequent events
|
// If the client(s) hasn't deleted the wayland object, just ignore subsequent events
|
||||||
if (auto *r = resource()->handle)
|
for (auto *r : resourceMap())
|
||||||
wl_resource_set_implementation(r, nullptr, nullptr, nullptr);
|
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)
|
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)
|
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);
|
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
|
} // namespace MockCompositor
|
||||||
|
@ -42,8 +42,14 @@ class DataDeviceManager : public Global, public QtWaylandServer::wl_data_device_
|
|||||||
public:
|
public:
|
||||||
explicit DataDeviceManager(CoreCompositor *compositor, int version = 1)
|
explicit DataDeviceManager(CoreCompositor *compositor, int version = 1)
|
||||||
: QtWaylandServer::wl_data_device_manager(compositor->m_display, version)
|
: 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:
|
protected:
|
||||||
void data_device_manager_get_data_device(Resource *resource, uint32_t id, ::wl_resource *seatResource) override;
|
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
|
class DataDevice : public QtWaylandServer::wl_data_device
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit DataDevice(::wl_client *client, int id, int version)
|
explicit DataDevice(DataDeviceManager *manager, Seat *seat)
|
||||||
: QtWaylandServer::wl_data_device(client, id, version)
|
: m_manager(manager)
|
||||||
|
, m_seat(seat)
|
||||||
{}
|
{}
|
||||||
~DataDevice() override;
|
~DataDevice() override;
|
||||||
void send_data_offer(::wl_resource *resource) = delete;
|
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 send_selection(::wl_resource *resource) = delete;
|
||||||
void sendSelection(DataOffer *offer);
|
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
|
class DataOffer : public QObject, public QtWaylandServer::wl_data_offer
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit DataOffer(::wl_client *client, int version)
|
explicit DataOffer(DataDevice *dataDevice, ::wl_client *client, int version)
|
||||||
: QtWaylandServer::wl_data_offer (client, 0, version)
|
: QtWaylandServer::wl_data_offer (client, 0, version)
|
||||||
|
, m_dataDevice(dataDevice)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
DataDevice *m_dataDevice = nullptr;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void receive(QString mimeType, int fd);
|
void receive(QString mimeType, int fd);
|
||||||
|
|
||||||
@ -77,7 +101,7 @@ protected:
|
|||||||
void data_offer_destroy_resource(Resource *resource) override;
|
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_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_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
|
} // namespace MockCompositor
|
||||||
|
@ -54,6 +54,7 @@ public:
|
|||||||
XdgToplevel *xdgToplevel(int i = 0) { return get<XdgWmBase>()->toplevel(i); }
|
XdgToplevel *xdgToplevel(int i = 0) { return get<XdgWmBase>()->toplevel(i); }
|
||||||
XdgPopup *xdgPopup(int i = 0) { return get<XdgWmBase>()->popup(i); }
|
XdgPopup *xdgPopup(int i = 0) { return get<XdgWmBase>()->popup(i); }
|
||||||
Pointer *pointer() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_pointer; }
|
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; }
|
Keyboard *keyboard() { auto *seat = get<Seat>(); Q_ASSERT(seat); return seat->m_keyboard; }
|
||||||
uint sendXdgShellPing();
|
uint sendXdgShellPing();
|
||||||
void xdgPingAndWaitForPong();
|
void xdgPingAndWaitForPong();
|
||||||
|
@ -236,6 +236,7 @@ void XdgPopup::xdg_popup_destroy(Resource *resource) {
|
|||||||
}
|
}
|
||||||
m_xdgSurface->m_popup = nullptr;
|
m_xdgSurface->m_popup = nullptr;
|
||||||
m_parentXdgSurface->m_popups.removeAll(this);
|
m_parentXdgSurface->m_popups.removeAll(this);
|
||||||
|
emit destroyRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace MockCompositor
|
} // namespace MockCompositor
|
||||||
|
@ -130,8 +130,9 @@ protected:
|
|||||||
void xdg_toplevel_set_min_size(Resource *resource, int32_t width, int32_t height) override;
|
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:
|
public:
|
||||||
explicit XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version = 1);
|
explicit XdgPopup(XdgSurface *xdgSurface, XdgSurface *parent, int id, int version = 1);
|
||||||
void sendConfigure(const QRect &geometry);
|
void sendConfigure(const QRect &geometry);
|
||||||
@ -141,6 +142,8 @@ public:
|
|||||||
XdgSurface *m_parentXdgSurface = nullptr;
|
XdgSurface *m_parentXdgSurface = nullptr;
|
||||||
bool m_grabbed = false;
|
bool m_grabbed = false;
|
||||||
uint m_grabSerial = 0;
|
uint m_grabSerial = 0;
|
||||||
|
signals:
|
||||||
|
void destroyRequested();
|
||||||
protected:
|
protected:
|
||||||
void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override;
|
void xdg_popup_grab(Resource *resource, ::wl_resource *seat, uint32_t serial) override;
|
||||||
void xdg_popup_destroy(Resource *resource) override;
|
void xdg_popup_destroy(Resource *resource) override;
|
||||||
|
@ -220,12 +220,14 @@ QSharedPointer<MockSurface> MockCompositor::surface()
|
|||||||
{
|
{
|
||||||
QSharedPointer<MockSurface> result;
|
QSharedPointer<MockSurface> result;
|
||||||
lock();
|
lock();
|
||||||
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
|
{
|
||||||
foreach (Impl::Surface *surface, surfaces) {
|
QVector<Impl::Surface *> surfaces = m_compositor->surfaces();
|
||||||
// we don't want to mistake the cursor surface for a window surface
|
foreach (Impl::Surface *surface, surfaces) {
|
||||||
if (surface->isMapped()) {
|
// we don't want to mistake the cursor surface for a window surface
|
||||||
result = surface->mockSurface();
|
if (surface->isMapped()) {
|
||||||
break;
|
result = surface->mockSurface();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unlock();
|
unlock();
|
||||||
@ -307,7 +309,7 @@ void *MockCompositor::run(void *data)
|
|||||||
{
|
{
|
||||||
MockCompositor *controller = static_cast<MockCompositor *>(data);
|
MockCompositor *controller = static_cast<MockCompositor *>(data);
|
||||||
|
|
||||||
Impl::Compositor compositor;
|
Impl::Compositor compositor(controller);
|
||||||
|
|
||||||
controller->m_compositor = &compositor;
|
controller->m_compositor = &compositor;
|
||||||
controller->m_waitCondition.wakeOne();
|
controller->m_waitCondition.wakeOne();
|
||||||
@ -332,8 +334,8 @@ void *MockCompositor::run(void *data)
|
|||||||
|
|
||||||
namespace Impl {
|
namespace Impl {
|
||||||
|
|
||||||
Compositor::Compositor()
|
Compositor::Compositor(MockCompositor *mockCompositor)
|
||||||
: m_display(wl_display_create())
|
: m_mockCompositor(mockCompositor), m_display(wl_display_create())
|
||||||
{
|
{
|
||||||
if (wl_display_add_socket(m_display, 0)) {
|
if (wl_display_add_socket(m_display, 0)) {
|
||||||
fprintf(stderr, "Fatal: Failed to open server socket\n");
|
fprintf(stderr, "Fatal: Failed to open server socket\n");
|
||||||
@ -445,15 +447,19 @@ uint32_t Compositor::nextSerial()
|
|||||||
|
|
||||||
void Compositor::addSurface(Surface *surface)
|
void Compositor::addSurface(Surface *surface)
|
||||||
{
|
{
|
||||||
|
m_mockCompositor->lock();
|
||||||
m_surfaces << surface;
|
m_surfaces << surface;
|
||||||
|
m_mockCompositor->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Compositor::removeSurface(Surface *surface)
|
void Compositor::removeSurface(Surface *surface)
|
||||||
{
|
{
|
||||||
|
m_mockCompositor->lock();
|
||||||
m_surfaces.removeOne(surface);
|
m_surfaces.removeOne(surface);
|
||||||
m_keyboard->handleSurfaceDestroyed(surface);
|
m_keyboard->handleSurfaceDestroyed(surface);
|
||||||
m_pointer->handleSurfaceDestroyed(surface);
|
m_pointer->handleSurfaceDestroyed(surface);
|
||||||
m_fullScreenShellV1->removeSurface(surface);
|
m_fullScreenShellV1->removeSurface(surface);
|
||||||
|
m_mockCompositor->unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface *Compositor::resolveSurface(const QVariant &v)
|
Surface *Compositor::resolveSurface(const QVariant &v)
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QWaitCondition>
|
#include <QWaitCondition>
|
||||||
|
|
||||||
|
class MockCompositor;
|
||||||
|
|
||||||
namespace Impl {
|
namespace Impl {
|
||||||
|
|
||||||
typedef void (**Implementation)(void);
|
typedef void (**Implementation)(void);
|
||||||
@ -63,7 +65,7 @@ class XdgShellV6;
|
|||||||
class Compositor
|
class Compositor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Compositor();
|
Compositor(MockCompositor *mockCompositor);
|
||||||
~Compositor();
|
~Compositor();
|
||||||
|
|
||||||
int fileDescriptor() const { return m_fd; }
|
int fileDescriptor() const { return m_fd; }
|
||||||
@ -114,6 +116,7 @@ private:
|
|||||||
|
|
||||||
void initShm();
|
void initShm();
|
||||||
|
|
||||||
|
MockCompositor *m_mockCompositor = nullptr;
|
||||||
QRect m_outputGeometry;
|
QRect m_outputGeometry;
|
||||||
|
|
||||||
wl_display *m_display = nullptr;
|
wl_display *m_display = nullptr;
|
||||||
|
@ -43,6 +43,7 @@ private slots:
|
|||||||
void configureStates();
|
void configureStates();
|
||||||
void popup();
|
void popup();
|
||||||
void tooltipOnPopup();
|
void tooltipOnPopup();
|
||||||
|
void switchPopups();
|
||||||
void pongs();
|
void pongs();
|
||||||
void minMaxSize();
|
void minMaxSize();
|
||||||
void windowGeometry();
|
void windowGeometry();
|
||||||
@ -332,6 +333,94 @@ void tst_xdgshell::tooltipOnPopup()
|
|||||||
QCOMPOSITOR_TRY_COMPARE(xdgPopup(0), nullptr);
|
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()
|
void tst_xdgshell::pongs()
|
||||||
{
|
{
|
||||||
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);
|
QSignalSpy pongSpy(exec([=] { return get<XdgWmBase>(); }), &XdgWmBase::pong);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user