// Copyright (C) 2016 The Qt Company Ltd. // Copyright (C) 2024 Jie Liu // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qwaylandinputdevice_p.h" #include "qwaylandintegration_p.h" #include "qwaylandtextinputv3_p.h" #include "qwaylandwindow_p.h" #include "qwaylandsurface_p.h" #include "qwaylandbuffer_p.h" #if QT_CONFIG(wayland_datadevice) #include "qwaylanddatadevice_p.h" #include "qwaylanddatadevicemanager_p.h" #endif #if QT_CONFIG(clipboard) #include "qwaylanddatacontrolv1_p.h" #endif #if QT_CONFIG(wayland_client_primary_selection) #include "qwaylandprimaryselectionv1_p.h" #endif #if QT_CONFIG(tabletevent) #include "qwaylandtabletv2_p.h" #endif #include "qwaylandpointergestures_p.h" #include "qwaylandscreen_p.h" #include "qwaylandcursor_p.h" #include "qwaylanddisplay_p.h" #include "qwaylandshmbackingstore_p.h" #include "qwaylandtextinputv1_p.h" #include "qwaylandtextinputv2_p.h" #include "qwaylandtextinputinterface_p.h" #include "qwaylandinputcontext_p.h" #include "qwaylandinputmethodcontext_p.h" #include "qwaylandcallback_p.h" #include "qwaylandcursorsurface_p.h" #include #include #include #include #include #include #include #include #include #if QT_CONFIG(cursor) #include #endif #include #include QT_BEGIN_NAMESPACE namespace QtWaylandClient { Q_LOGGING_CATEGORY(lcQpaWaylandInput, "qt.qpa.wayland.input"); // The maximum number of concurrent touchpoints is not exposed in wayland, so we assume a // reasonable number of them. As of 2021 most touchscreen panels support 10 concurrent touchpoints. static const int MaxTouchPoints = 10; QWaylandInputDevice::Keyboard::Keyboard(QWaylandInputDevice *p) : mParent(p) { init(p->get_keyboard()); mRepeatTimer.callOnTimeout(this, [&]() { if (!focusWindow()) { // We destroyed the keyboard focus surface, but the server didn't get the message yet... // or the server didn't send an enter event first. return; } mRepeatTimer.setInterval(1000 / mRepeatRate); Qt::KeyboardModifiers modifiers = this->modifiers(); handleKey(mRepeatKey.time, QEvent::KeyRelease, mRepeatKey.key, modifiers, mRepeatKey.code, mRepeatKey.nativeVirtualKey, this->mNativeModifiers, mRepeatKey.text, true); handleKey(mRepeatKey.time, QEvent::KeyPress, mRepeatKey.key, modifiers, mRepeatKey.code, mRepeatKey.nativeVirtualKey, this->mNativeModifiers, mRepeatKey.text, true); }); } #if QT_CONFIG(xkbcommon) bool QWaylandInputDevice::Keyboard::createDefaultKeymap() { struct xkb_context *ctx = mParent->mQDisplay->xkbContext(); if (!ctx) return false; struct xkb_rule_names names; names.rules = "evdev"; names.model = "pc105"; names.layout = "us"; names.variant = ""; names.options = ""; mXkbKeymap.reset(xkb_keymap_new_from_names(ctx, &names, XKB_KEYMAP_COMPILE_NO_FLAGS)); if (mXkbKeymap) mXkbState.reset(xkb_state_new(mXkbKeymap.get())); if (!mXkbKeymap || !mXkbState) { qCWarning(lcQpaWayland, "failed to create default keymap"); return false; } return true; } #endif QWaylandInputDevice::Keyboard::~Keyboard() { if (mFocus) QWindowSystemInterface::handleFocusWindowChanged(nullptr); if (version() >= 3) wl_keyboard_release(object()); else wl_keyboard_destroy(object()); } QWaylandWindow *QWaylandInputDevice::Keyboard::focusWindow() const { return mFocus ? mFocus->waylandWindow() : nullptr; } QWaylandInputDevice::Pointer::Pointer(QWaylandInputDevice *seat) : mParent(seat) { init(seat->get_pointer()); #if QT_CONFIG(cursor) if (auto cursorShapeManager = seat->mQDisplay->cursorShapeManager()) { mCursor.shape.reset(new QWaylandCursorShape(cursorShapeManager->get_pointer(object()))); } mCursor.frameTimer.setSingleShot(true); mCursor.frameTimer.callOnTimeout(this, [&]() { cursorTimerCallback(); }); #endif } QWaylandInputDevice::Pointer::~Pointer() { if (version() >= 3) wl_pointer_release(object()); else wl_pointer_destroy(object()); } QWaylandWindow *QWaylandInputDevice::Pointer::focusWindow() const { return mFocus ? mFocus->waylandWindow() : nullptr; } #if QT_CONFIG(cursor) int QWaylandInputDevice::Pointer::idealCursorScale() const { if (seat()->mQDisplay->compositor()->version() < 3) { return 1; } if (auto *s = mCursor.surface.data()) { if (s->outputScale() > 0) return s->outputScale(); } return seat()->mCursor.fallbackOutputScale; } void QWaylandInputDevice::Pointer::updateCursorTheme() { QString cursorThemeName; QSize cursorSize; if (const QPlatformTheme *platformTheme = QGuiApplicationPrivate::platformTheme()) { cursorThemeName = platformTheme->themeHint(QPlatformTheme::MouseCursorTheme).toString(); cursorSize = platformTheme->themeHint(QPlatformTheme::MouseCursorSize).toSize(); } if (cursorThemeName.isEmpty()) cursorThemeName = QStringLiteral("default"); if (cursorSize.isEmpty()) cursorSize = QSize(24, 24); int scale = idealCursorScale(); int pixelSize = cursorSize.width() * scale; auto *display = seat()->mQDisplay; mCursor.theme = display->loadCursorTheme(cursorThemeName, pixelSize); if (!mCursor.theme) return; // A warning has already been printed in loadCursorTheme if (auto *arrow = mCursor.theme->cursor(Qt::ArrowCursor)) { int arrowPixelSize = qMax(arrow->images[0]->width, arrow->images[0]->height); // Not all cursor themes are square while (scale > 1 && arrowPixelSize / scale < cursorSize.width()) --scale; } else { qCWarning(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->reset(); set_cursor(mEnterSerial, nullptr, 0, 0); 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.shape) { if (mCursor.surface) { mCursor.surface->reset(); } mCursor.shape->setShape(mEnterSerial, shape); return; } if (!mCursor.theme || idealCursorScale() != mCursor.themeBufferScale) updateCursorTheme(); if (!mCursor.theme) return; // Set from shape using theme const QElapsedTimer &timer = seat()->mCursor.animationTimer; const uint time = timer.isValid() ? timer.elapsed() : 0; if (struct ::wl_cursor *waylandCursor = mCursor.theme->cursor(shape)) { uint duration = 0; int frame = wl_cursor_frame_and_duration(waylandCursor, time, &duration); ::wl_cursor_image *image = waylandCursor->images[frame]; struct wl_buffer *buffer = wl_cursor_image_get_buffer(image); if (!buffer) { qCWarning(lcQpaWayland) << "Could not find buffer for cursor" << shape; return; } int bufferScale = mCursor.themeBufferScale; QPoint hotspot = QPoint(image->hotspot_x, image->hotspot_y) / bufferScale; QSize size = QSize(image->width, image->height) / bufferScale; bool animated = duration > 0; if (animated) { mCursor.gotFrameCallback = false; mCursor.gotTimerCallback = false; mCursor.frameTimer.start(duration); } getOrCreateCursorSurface()->update(buffer, hotspot, size, bufferScale, animated); 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(); } void QWaylandInputDevice::Pointer::cursorTimerCallback() { mCursor.gotTimerCallback = true; if (mCursor.gotFrameCallback) { updateCursor(); } } void QWaylandInputDevice::Pointer::cursorFrameCallback() { mCursor.gotFrameCallback = true; if (mCursor.gotTimerCallback) { updateCursor(); } } #endif // QT_CONFIG(cursor) QWaylandInputDevice::Touch::Touch(QWaylandInputDevice *p) : mParent(p) { init(p->get_touch()); } QWaylandInputDevice::Touch::~Touch() { if (version() >= 3) wl_touch_release(object()); else wl_touch_destroy(object()); } QWaylandInputDevice::QWaylandInputDevice(QWaylandDisplay *display, int version, uint32_t id) : QtWayland::wl_seat(display->wl_registry(), id, qMin(version, 9)) , mQDisplay(display) , mDisplay(display->wl_display()) , mId(id) { #if QT_CONFIG(clipboard) if (auto *dataControlManager = mQDisplay->dataControlManager()) { setDataControlDevice(dataControlManager->createDevice(this)); } #endif #if QT_CONFIG(wayland_datadevice) if (mQDisplay->dndSelectionHandler()) { mDataDevice = mQDisplay->dndSelectionHandler()->getDataDevice(this); } #endif #if QT_CONFIG(wayland_client_primary_selection) // TODO: Could probably decouple this more if there was a signal for new seat added if (auto *psm = mQDisplay->primarySelectionManager()) setPrimarySelectionDevice(psm->createDevice(this)); #endif if (mQDisplay->textInputManagerv1()) { auto textInput = new QWaylandTextInputv1(mQDisplay, mQDisplay->textInputManagerv1()->create_text_input()); textInput->setSeat(wl_seat()); mTextInput.reset(textInput); } if (mQDisplay->textInputManagerv2()) mTextInput.reset(new QWaylandTextInputv2(mQDisplay, mQDisplay->textInputManagerv2()->get_text_input(wl_seat()))); if (mQDisplay->textInputManagerv3()) mTextInput.reset(new QWaylandTextInputv3(mQDisplay, mQDisplay->textInputManagerv3()->get_text_input(wl_seat()))); if (mQDisplay->textInputMethodManager()) mTextInputMethod.reset(new QWaylandTextInputMethod(mQDisplay, mQDisplay->textInputMethodManager()->get_text_input_method(wl_seat()))); #if QT_CONFIG(tabletevent) if (auto *tm = mQDisplay->tabletManager()) mTabletSeat.reset(new QWaylandTabletSeatV2(tm, this)); #endif } QWaylandInputDevice::~QWaylandInputDevice() { if (version() >= WL_SEAT_RELEASE_SINCE_VERSION) release(); else wl_seat_destroy(object()); } void QWaylandInputDevice::seat_capabilities(uint32_t caps) { mCaps = caps; maybeRegisterInputDevices(); } void QWaylandInputDevice::seat_name(const QString &name) { mSeatName = name; mSeatNameKnown = true; maybeRegisterInputDevices(); } void QWaylandInputDevice::maybeRegisterInputDevices() { if (!mSeatNameKnown) return; // too early if (mCaps & WL_SEAT_CAPABILITY_KEYBOARD && !mKeyboard) { mKeyboard.reset(createKeyboard(this)); } else if (!(mCaps & WL_SEAT_CAPABILITY_KEYBOARD) && mKeyboard) { mKeyboard.reset(); } if (mCaps & WL_SEAT_CAPABILITY_POINTER && !mPointer) { mPointer.reset(createPointer(this)); auto *pointerGestures = mQDisplay->pointerGestures(); if (pointerGestures) { // NOTE: The name of the device and its system ID are not exposed on Wayland. mTouchPadDevice = new QPointingDevice( QLatin1StringView("touchpad"), 0, QInputDevice::DeviceType::TouchPad, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, MaxTouchPoints, 0, mSeatName, QPointingDeviceUniqueId(), this); QWindowSystemInterface::registerInputDevice(mTouchPadDevice); mPointerGesturePinch.reset(pointerGestures->createPointerGesturePinch(this)); mPointerGesturePinch->init(pointerGestures->get_pinch_gesture(mPointer->object())); mPointerGestureSwipe.reset(pointerGestures->createPointerGestureSwipe(this)); mPointerGestureSwipe->init(pointerGestures->get_swipe_gesture(mPointer->object())); } } else if (!(mCaps & WL_SEAT_CAPABILITY_POINTER) && mPointer) { mPointer.reset(); mPointerGesturePinch.reset(); mPointerGestureSwipe.reset(); } if (mCaps & WL_SEAT_CAPABILITY_TOUCH && !mTouch) { mTouch.reset(createTouch(this)); if (!mTouchDevice) { // TODO number of touchpoints, actual name and ID mTouchDevice = new QPointingDevice( QLatin1StringView("touchscreen"), 0, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position, MaxTouchPoints, 0, mSeatName, QPointingDeviceUniqueId(), this); QWindowSystemInterface::registerInputDevice(mTouchDevice); } } else if (!(mCaps & WL_SEAT_CAPABILITY_TOUCH) && mTouch) { mTouch.reset(); } } QWaylandInputDevice::Keyboard *QWaylandInputDevice::createKeyboard(QWaylandInputDevice *device) { return new Keyboard(device); } QWaylandInputDevice::Pointer *QWaylandInputDevice::createPointer(QWaylandInputDevice *device) { return new Pointer(device); } QWaylandInputDevice::Touch *QWaylandInputDevice::createTouch(QWaylandInputDevice *device) { return new Touch(device); } QWaylandInputDevice::Keyboard *QWaylandInputDevice::keyboard() const { return mKeyboard.data(); } QWaylandInputDevice::Pointer *QWaylandInputDevice::pointer() const { return mPointer.data(); } QWaylandPointerGestureSwipe *QWaylandInputDevice::pointerGestureSwipe() const { return mPointerGestureSwipe.data(); } QWaylandPointerGesturePinch *QWaylandInputDevice::pointerGesturePinch() const { return mPointerGesturePinch.data(); } QWaylandInputDevice::Touch *QWaylandInputDevice::touch() const { return mTouch.data(); } void QWaylandInputDevice::handleEndDrag() { if (mTouch) mTouch->releasePoints(); if (mPointer) mPointer->releaseButtons(); } void QWaylandInputDevice::handleStartDrag() { if (mPointer) mPointer->leavePointers(); } #if QT_CONFIG(wayland_datadevice) void QWaylandInputDevice::setDataDevice(QWaylandDataDevice *device) { mDataDevice = device; } QWaylandDataDevice *QWaylandInputDevice::dataDevice() const { return mDataDevice; } #endif #if QT_CONFIG(clipboard) void QWaylandInputDevice::setDataControlDevice(QWaylandDataControlDeviceV1 *dataControlDevice) { mDataControlDevice.reset(dataControlDevice); } QWaylandDataControlDeviceV1 *QWaylandInputDevice::dataControlDevice() const { return mDataControlDevice.data(); } #endif #if QT_CONFIG(wayland_client_primary_selection) void QWaylandInputDevice::setPrimarySelectionDevice(QWaylandPrimarySelectionDeviceV1 *primarySelectionDevice) { mPrimarySelectionDevice.reset(primarySelectionDevice); } QWaylandPrimarySelectionDeviceV1 *QWaylandInputDevice::primarySelectionDevice() const { return mPrimarySelectionDevice.data(); } #endif void QWaylandInputDevice::setTextInput(QWaylandTextInputInterface *textInput) { mTextInput.reset(textInput); } #if QT_CONFIG(tabletevent) void QWaylandInputDevice::setTabletSeat(QWaylandTabletSeatV2 *tabletSeat) { mTabletSeat.reset(tabletSeat); } QWaylandTabletSeatV2 *QWaylandInputDevice::tabletSeat() const { return mTabletSeat.get(); } #endif void QWaylandInputDevice::setTextInputMethod(QWaylandTextInputMethod *textInputMethod) { mTextInputMethod.reset(textInputMethod); } QWaylandTextInputInterface *QWaylandInputDevice::textInput() const { return mTextInput.data(); } QWaylandTextInputMethod *QWaylandInputDevice::textInputMethod() const { return mTextInputMethod.data(); } void QWaylandInputDevice::removeMouseButtonFromState(Qt::MouseButton button) { if (mPointer) mPointer->mButtons = mPointer->mButtons & !button; } QWaylandWindow *QWaylandInputDevice::pointerFocus() const { return mPointer ? mPointer->focusWindow() : nullptr; } QWaylandWindow *QWaylandInputDevice::keyboardFocus() const { return mKeyboard ? mKeyboard->focusWindow() : nullptr; } QWaylandWindow *QWaylandInputDevice::touchFocus() const { return mTouch ? mTouch->mFocus : nullptr; } QPointF QWaylandInputDevice::pointerSurfacePosition() const { return mPointer ? mPointer->mSurfacePos : QPointF(); } QList QWaylandInputDevice::possibleKeys(const QKeyEvent *event) const { #if QT_CONFIG(xkbcommon) if (mKeyboard && mKeyboard->mXkbState) return QXkbCommon::possibleKeys(mKeyboard->mXkbState.get(), event); #else Q_UNUSED(event); #endif return {}; } Qt::KeyboardModifiers QWaylandInputDevice::modifiers() const { if (!mKeyboard) return Qt::NoModifier; return mKeyboard->modifiers(); } Qt::KeyboardModifiers QWaylandInputDevice::Keyboard::modifiers() const { Qt::KeyboardModifiers ret = Qt::NoModifier; #if QT_CONFIG(xkbcommon) if (!mXkbState) return ret; ret = QXkbCommon::modifiers(mXkbState.get()); #endif return ret; } #if QT_CONFIG(cursor) void QWaylandInputDevice::setCursor(const QCursor *cursor, const QSharedPointer &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; mCursor.animationTimer.start(); 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) mPointer->updateCursor(); #if QT_CONFIG(tabletevent) if (mTabletSeat) mTabletSeat->updateCursor(); #endif } #endif class EnterEvent : public QWaylandPointerEvent { public: EnterEvent(QWaylandWindow *surface, const QPointF &local, const QPointF &global) : QWaylandPointerEvent(QEvent::Enter, Qt::NoScrollPhase, surface, 0, local, global, Qt::NoButton, Qt::NoButton, Qt::NoModifier) {} }; void QWaylandInputDevice::Pointer::pointer_enter(uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) { if (!surface) return; QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); if (!window) return; // Ignore foreign surfaces if (mFocus) { qCWarning(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->waylandSurface(); connect(mFocus.data(), &QObject::destroyed, this, &Pointer::handleFocusDestroyed); mSurfacePos = QPointF(wl_fixed_to_double(sx), wl_fixed_to_double(sy)); mGlobalPos = window->mapToGlobalF(mSurfacePos); mParent->mSerial = serial; mEnterSerial = serial; if (!mParent->mQDisplay->lastInputDevice()) { mParent->mQDisplay->setLastInputDevice(mParent, serial, window); } #if QT_CONFIG(cursor) // Depends on mEnterSerial being updated updateCursor(); #endif QWaylandWindow *grab = QWaylandWindow::mouseGrab(); if (!grab) setFrameEvent(new EnterEvent(window, mSurfacePos, mGlobalPos)); } class LeaveEvent : public QWaylandPointerEvent { public: LeaveEvent(QWaylandWindow *surface, const QPointF &localPos, const QPointF &globalPos) : QWaylandPointerEvent(QEvent::Leave, Qt::NoScrollPhase, surface, 0, localPos, globalPos, Qt::NoButton, Qt::NoButton, Qt::NoModifier) {} }; void QWaylandInputDevice::Pointer::pointer_leave(uint32_t serial, struct wl_surface *surface) { invalidateFocus(); mButtons = Qt::NoButton; mParent->mSerial = serial; // The event may arrive after destroying the window, indicated by // a null surface. if (!surface) return; auto *window = QWaylandWindow::fromWlSurface(surface); if (!window) return; // Ignore foreign surfaces if (!QWaylandWindow::mouseGrab()) setFrameEvent(new LeaveEvent(window, mSurfacePos, mGlobalPos)); } class MotionEvent : public QWaylandPointerEvent { public: MotionEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, const QPointF &globalPos, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers) : QWaylandPointerEvent(QEvent::MouseMove, Qt::NoScrollPhase, surface, timestamp, localPos, globalPos, buttons, Qt::NoButton, modifiers) { } }; void QWaylandInputDevice::Pointer::pointer_motion(uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) { QWaylandWindow *window = focusWindow(); if (!window) { // We destroyed the pointer 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. return; } QPointF pos(wl_fixed_to_double(surface_x), wl_fixed_to_double(surface_y)); QPointF global = window->mapToGlobalF(pos); mSurfacePos = pos; mGlobalPos = global; mParent->mTime = time; QWaylandWindow *grab = QWaylandWindow::mouseGrab(); if (grab && grab != window) { // We can't know the true position since we're getting events for another surface, // so we just set it outside of the window boundaries. pos = QPointF(-1, -1); global = grab->mapToGlobalF(pos); window = grab; } setFrameEvent(new MotionEvent(window, time, pos, global, mButtons, mParent->modifiers())); } class PressEvent : public QWaylandPointerEvent { public: PressEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, const QPointF &globalPos, Qt::MouseButtons buttons, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) : QWaylandPointerEvent(QEvent::MouseButtonPress, Qt::NoScrollPhase, surface, timestamp, localPos, globalPos, buttons, button, modifiers) { } }; class ReleaseEvent : public QWaylandPointerEvent { public: ReleaseEvent(QWaylandWindow *surface, ulong timestamp, const QPointF &localPos, const QPointF &globalPos, Qt::MouseButtons buttons, Qt::MouseButton button, Qt::KeyboardModifiers modifiers) : QWaylandPointerEvent(QEvent::MouseButtonRelease, Qt::NoScrollPhase, surface, timestamp, localPos, globalPos, buttons, button, modifiers) { } }; void QWaylandInputDevice::Pointer::pointer_button(uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { QWaylandWindow *window = focusWindow(); if (!window) { // We destroyed the pointer 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. return; } Qt::MouseButton qt_button; // translate from kernel (input.h) 'button' to corresponding Qt:MouseButton. // The range of mouse values is 0x110 <= mouse_button < 0x120, the first Joystick button. switch (button) { case 0x110: qt_button = Qt::LeftButton; break; // kernel BTN_LEFT case 0x111: qt_button = Qt::RightButton; break; case 0x112: qt_button = Qt::MiddleButton; break; case 0x113: qt_button = Qt::ExtraButton1; break; // AKA Qt::BackButton case 0x114: qt_button = Qt::ExtraButton2; break; // AKA Qt::ForwardButton case 0x115: qt_button = Qt::ExtraButton3; break; // AKA Qt::TaskButton case 0x116: qt_button = Qt::ExtraButton4; break; case 0x117: qt_button = Qt::ExtraButton5; break; case 0x118: qt_button = Qt::ExtraButton6; break; case 0x119: qt_button = Qt::ExtraButton7; break; case 0x11a: qt_button = Qt::ExtraButton8; break; case 0x11b: qt_button = Qt::ExtraButton9; break; case 0x11c: qt_button = Qt::ExtraButton10; break; case 0x11d: qt_button = Qt::ExtraButton11; break; case 0x11e: qt_button = Qt::ExtraButton12; break; case 0x11f: qt_button = Qt::ExtraButton13; break; default: return; // invalid button number (as far as Qt is concerned) } mLastButton = qt_button; if (state) mButtons |= qt_button; else mButtons &= ~qt_button; mParent->mTime = time; mParent->mSerial = serial; if (state) mParent->mQDisplay->setLastInputDevice(mParent, serial, window); QWaylandWindow *grab = QWaylandWindow::mouseGrab(); QPointF pos = mSurfacePos; QPointF global = mGlobalPos; if (grab && grab != focusWindow()) { pos = QPointF(-1, -1); global = grab->mapToGlobalF(pos); window = grab; } if (state) setFrameEvent(new PressEvent(window, time, pos, global, mButtons, qt_button, mParent->modifiers())); else setFrameEvent(new ReleaseEvent(window, time, pos, global, mButtons, qt_button, mParent->modifiers())); } void QWaylandInputDevice::Pointer::invalidateFocus() { if (mFocus) { disconnect(mFocus.data(), &QObject::destroyed, this, &Pointer::handleFocusDestroyed); mFocus = nullptr; } mEnterSerial = 0; } void QWaylandInputDevice::Pointer::releaseButtons() { setFrameEvent(new ReleaseEvent(nullptr, mParent->mTime, mSurfacePos, mGlobalPos, Qt::NoButton, Qt::NoButton, mParent->modifiers())); } void QWaylandInputDevice::Pointer::leavePointers() { if (auto *window = focusWindow()) { LeaveEvent e(focusWindow(), mSurfacePos, mGlobalPos); window->handleMouse(mParent, e); } } class WheelEvent : public QWaylandPointerEvent { public: WheelEvent(QWaylandWindow *surface, Qt::ScrollPhase phase, ulong timestamp, const QPointF &local, const QPointF &global, const QPoint &pixelDelta, const QPoint &angleDelta, Qt::MouseEventSource source, Qt::KeyboardModifiers modifiers, bool inverted) : QWaylandPointerEvent(QEvent::Wheel, phase, surface, timestamp, local, global, modifiers & Qt::AltModifier ? pixelDelta.transposed() : pixelDelta, modifiers & Qt::AltModifier ? angleDelta.transposed() : angleDelta, source, modifiers, inverted) { } }; void QWaylandInputDevice::Pointer::pointer_axis(uint32_t time, uint32_t axis, int32_t value) { if (!focusWindow()) { // We destroyed the pointer 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. return; } // Get the delta and convert it into the expected range switch (axis) { case WL_POINTER_AXIS_VERTICAL_SCROLL: mFrameData.delta.ry() += wl_fixed_to_double(value); qCDebug(lcQpaWaylandInput) << "wl_pointer.axis vertical:" << mFrameData.delta.y(); break; case WL_POINTER_AXIS_HORIZONTAL_SCROLL: mFrameData.delta.rx() += wl_fixed_to_double(value); qCDebug(lcQpaWaylandInput) << "wl_pointer.axis horizontal:" << mFrameData.delta.x(); break; default: //TODO: is this really needed? qCWarning(lcQpaWaylandInput) << "wl_pointer.axis: Unknown axis:" << axis; return; } mParent->mTime = time; if (version() < WL_POINTER_FRAME_SINCE_VERSION) { qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; flushFrameEvent(); } } void QWaylandInputDevice::Pointer::pointer_frame() { flushFrameEvent(); } void QWaylandInputDevice::Pointer::pointer_axis_source(uint32_t source) { switch (source) { case axis_source_wheel: qCDebug(lcQpaWaylandInput) << "Axis source wheel"; break; case axis_source_finger: qCDebug(lcQpaWaylandInput) << "Axis source finger"; break; case axis_source_continuous: qCDebug(lcQpaWaylandInput) << "Axis source continuous"; break; case axis_source_wheel_tilt: qCDebug(lcQpaWaylandInput) << "Axis source wheel tilt"; } mFrameData.axisSource = axis_source(source); } void QWaylandInputDevice::Pointer::pointer_axis_stop(uint32_t time, uint32_t axis) { if (!focusWindow()) return; mParent->mTime = time; switch (axis) { case axis_vertical_scroll: qCDebug(lcQpaWaylandInput) << "Received vertical wl_pointer.axis_stop"; mFrameData.delta.setY(0); //TODO: what's the point of doing this? break; case axis_horizontal_scroll: qCDebug(lcQpaWaylandInput) << "Received horizontal wl_pointer.axis_stop"; mFrameData.delta.setX(0); break; default: qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_stop: Unknown axis: " << axis << "This is most likely a compositor bug"; return; } // May receive axis_stop for events we haven't sent a ScrollBegin for because // most axis_sources do not mandate an axis_stop event to be sent. if (!mScrollBeginSent) { // TODO: For now, we just ignore these events, but we could perhaps take this as an // indication that this compositor will in fact send axis_stop events for these sources // and send a ScrollBegin the next time an axis_source event with this type is encountered. return; } QWaylandWindow *target = QWaylandWindow::mouseGrab(); if (!target) target = focusWindow(); Qt::KeyboardModifiers mods = mParent->modifiers(); const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; WheelEvent wheelEvent(focusWindow(), Qt::ScrollEnd, mParent->mTime, mSurfacePos, mGlobalPos, QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mods, inverted); target->handleMouse(mParent, wheelEvent); mScrollBeginSent = false; mScrollDeltaRemainder = QPointF(); } void QWaylandInputDevice::Pointer::pointer_axis_discrete(uint32_t axis, int32_t value) { if (!focusWindow()) return; const int32_t delta120 = value * 15 * 8; switch (axis) { case axis_vertical_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete vertical:" << value; mFrameData.delta120.ry() += delta120; break; case axis_horizontal_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_discrete horizontal:" << value; mFrameData.delta120.rx() += delta120; break; default: //TODO: is this really needed? qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_discrete: Unknown axis:" << axis; return; } } void QWaylandInputDevice::Pointer::pointer_axis_value120(uint32_t axis, int32_t value) { if (!focusWindow()) return; switch (axis) { case axis_vertical_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 vertical:" << value; mFrameData.delta120.ry() += value; break; case axis_horizontal_scroll: qCDebug(lcQpaWaylandInput) << "wl_pointer.axis_value120 horizontal:" << value; mFrameData.delta120.rx() += value; break; default: qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_value120: Unknown axis:" << axis; return; } } void QWaylandInputDevice::Pointer::pointer_axis_relative_direction(uint32_t axis, uint32_t direction) { const bool inverted = direction == axis_relative_direction_inverted; switch (axis) { case axis_vertical_scroll: mFrameData.verticalAxisInverted = inverted; break; case axis_horizontal_scroll: mFrameData.horizontalAxisInverted = inverted; break; default: qCWarning(lcQpaWaylandInput) << "wl_pointer.axis_relative_direction: Unknown axis:" << axis; } } void QWaylandInputDevice::Pointer::setFrameEvent(QWaylandPointerEvent *event) { qCDebug(lcQpaWaylandInput) << "Setting frame event " << event->type; if (mFrameData.event && mFrameData.event->type != event->type) { qCDebug(lcQpaWaylandInput) << "Flushing; previous was " << mFrameData.event->type; flushFrameEvent(); } mFrameData.event = event; if (version() < WL_POINTER_FRAME_SINCE_VERSION) { qCDebug(lcQpaWaylandInput) << "Flushing new event; no frame event in this version"; flushFrameEvent(); } } void QWaylandInputDevice::Pointer::FrameData::resetScrollData() { delta120 = QPoint(); delta = QPointF(); axisSource = axis_source_wheel; horizontalAxisInverted = false; verticalAxisInverted = false; } bool QWaylandInputDevice::Pointer::FrameData::hasPixelDelta() const { switch (axisSource) { case axis_source_wheel_tilt: // sideways tilt of the wheel case axis_source_wheel: // In the case of wheel events, a pixel delta doesn't really make sense, // and will make Qt think this is a continuous scroll event when it isn't, // so just ignore it. return false; case axis_source_finger: case axis_source_continuous: return !delta.isNull(); default: return false; } } QPoint QWaylandInputDevice::Pointer::FrameData::pixelDeltaAndError(QPointF *accumulatedError) const { if (!hasPixelDelta()) return QPoint(); Q_ASSERT(accumulatedError); // Add accumulated rounding error before rounding again QPoint pixelDelta = (delta + *accumulatedError).toPoint(); *accumulatedError += delta - pixelDelta; Q_ASSERT(qAbs(accumulatedError->x()) < 1.0); Q_ASSERT(qAbs(accumulatedError->y()) < 1.0); // for continuous scroll events things should be // in the same direction // i.e converted so downwards surface co-ordinates (positive axis_value) // goes to downwards in wheel event (negative value) pixelDelta *= -1; return pixelDelta; } QPoint QWaylandInputDevice::Pointer::FrameData::angleDelta() const { if (delta120.isNull()) { // If we didn't get any discrete events, then we need to fall back to // the continuous information. return (delta * -12).toPoint(); //TODO: why multiply by 12? } // The angle delta is in eights of degrees, and our docs says most mice have // 1 click = 15 degrees, i.e. 120 is one click. It's also in the opposite // direction of surface space. return -delta120; } Qt::MouseEventSource QWaylandInputDevice::Pointer::FrameData::wheelEventSource() const { switch (axisSource) { case axis_source_wheel_tilt: // sideways tilt of the wheel case axis_source_wheel: return Qt::MouseEventNotSynthesized; case axis_source_finger: case axis_source_continuous: default: // Whatever other sources might be added are probably not mouse wheels return Qt::MouseEventSynthesizedBySystem; } } void QWaylandInputDevice::Pointer::flushScrollEvent() { QPoint angleDelta = mFrameData.angleDelta(); // Angle delta is required for Qt wheel events, so don't try to send events if it's zero if (!angleDelta.isNull()) { QWaylandWindow *target = QWaylandWindow::mouseGrab(); if (!target) target = focusWindow(); if (isDefinitelyTerminated(mFrameData.axisSource) && !mScrollBeginSent) { qCDebug(lcQpaWaylandInput) << "Flushing scroll event sending ScrollBegin"; target->handleMouse(mParent, WheelEvent(focusWindow(), Qt::ScrollBegin, mParent->mTime, mSurfacePos, mGlobalPos, QPoint(), QPoint(), Qt::MouseEventNotSynthesized, mParent->modifiers(), false)); mScrollBeginSent = true; mScrollDeltaRemainder = QPointF(); } Qt::ScrollPhase phase = mScrollBeginSent ? Qt::ScrollUpdate : Qt::NoScrollPhase; QPoint pixelDelta = mFrameData.pixelDeltaAndError(&mScrollDeltaRemainder); Qt::MouseEventSource source = mFrameData.wheelEventSource(); // The wayland protocol has separate horizontal and vertical axes, Qt has just the one inverted flag // Pragmatically it should't come up const bool inverted = mFrameData.verticalAxisInverted || mFrameData.horizontalAxisInverted; qCDebug(lcQpaWaylandInput) << "Flushing scroll event" << phase << pixelDelta << angleDelta; target->handleMouse(mParent, WheelEvent(focusWindow(), phase, mParent->mTime, mSurfacePos, mGlobalPos, pixelDelta, angleDelta, source, mParent->modifiers(), inverted)); } mFrameData.resetScrollData(); } void QWaylandInputDevice::Pointer::flushFrameEvent() { if (auto *event = mFrameData.event) { if (auto window = event->surface) { window->handleMouse(mParent, *event); } else if (mFrameData.event->type == QEvent::MouseButtonRelease) { // If the window has been destroyed, we still need to report an up event, but it can't // be handled by the destroyed window (obviously), so send the event here instead. QWindowSystemInterface::handleMouseEvent( nullptr, event->timestamp, QPointingDevice::primaryPointingDevice(mParent->seatname()), event->local, event->global, event->buttons, event->button, event->type, event->modifiers); // , Qt::MouseEventSource source = // Qt::MouseEventNotSynthesized); } delete mFrameData.event; mFrameData.event = nullptr; } //TODO: do modifiers get passed correctly here? flushScrollEvent(); } bool QWaylandInputDevice::Pointer::isDefinitelyTerminated(QtWayland::wl_pointer::axis_source source) const { return source == axis_source_finger; } void QWaylandInputDevice::Keyboard::keyboard_keymap(uint32_t format, int32_t fd, uint32_t size) { mKeymapFormat = format; #if QT_CONFIG(xkbcommon) if (format == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) return; if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { qCWarning(lcQpaWayland) << "unknown keymap format:" << format; close(fd); return; } char *map_str = static_cast(mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0)); if (map_str == MAP_FAILED) { close(fd); return; } mXkbKeymap.reset(xkb_keymap_new_from_string(mParent->mQDisplay->xkbContext(), map_str, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS)); QXkbCommon::verifyHasLatinLayout(mXkbKeymap.get()); munmap(map_str, size); close(fd); if (mXkbKeymap) mXkbState.reset(xkb_state_new(mXkbKeymap.get())); else mXkbState.reset(nullptr); #else Q_UNUSED(fd); Q_UNUSED(size); #endif } void QWaylandInputDevice::Keyboard::keyboard_enter(uint32_t serial, struct wl_surface *surface, struct wl_array *keys) { Q_UNUSED(serial); Q_UNUSED(keys); 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; } QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); if (!window) return; if (mFocus) { qCWarning(lcQpaWayland()) << "Unexpected wl_keyboard.enter event. Keyboard already has focus"; disconnect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); } mFocus = window->waylandSurface(); connect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); mParent->mQDisplay->handleKeyboardFocusChanged(mParent); } void QWaylandInputDevice::Keyboard::keyboard_leave(uint32_t serial, struct wl_surface *surface) { Q_UNUSED(serial); if (!surface) { // Either a compositor bug, or a race condition with wl_surface.destroy, ignore the event. return; } QWaylandWindow *window = QWaylandWindow::fromWlSurface(surface); if (!window) return; if (window->waylandSurface() != mFocus) { qCWarning(lcQpaWayland) << "Ignoring unexpected wl_keyboard.leave event." << "wl_surface argument does not match the current focus" << "This is most likely a compositor bug"; return; } disconnect(mFocus, &QWaylandSurface::destroyed, this, &Keyboard::handleFocusDestroyed); handleFocusLost(); } void QWaylandInputDevice::Keyboard::handleKey(ulong timestamp, QEvent::Type type, int key, Qt::KeyboardModifiers modifiers, quint32 nativeScanCode, quint32 nativeVirtualKey, quint32 nativeModifiers, const QString &text, bool autorepeat, ushort count) { QPlatformInputContext *inputContext = QGuiApplicationPrivate::platformIntegration()->inputContext(); bool filtered = false; if (inputContext) { QKeyEvent event(type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorepeat, count); event.setTimestamp(timestamp); filtered = inputContext->filterEvent(&event); } if (!filtered) { auto window = focusWindow()->window(); if (type == QEvent::KeyPress && key == Qt::Key_Menu) { auto cursor = window->screen()->handle()->cursor(); if (cursor) { const QPoint globalPos = cursor->pos(); const QPoint pos = window->mapFromGlobal(globalPos); QWindowSystemInterface::handleContextMenuEvent(window, false, pos, globalPos, modifiers); } } QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, type, key, modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, autorepeat, count); } } void QWaylandInputDevice::Keyboard::keyboard_key(uint32_t serial, uint32_t time, uint32_t key, uint32_t state) { if (mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1 && mKeymapFormat != WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { qCWarning(lcQpaWayland) << Q_FUNC_INFO << "unknown keymap format:" << mKeymapFormat; return; } auto *window = focusWindow(); if (!window) { // We destroyed the keyboard focus surface, but the server didn't get the message yet... // or the server didn't send an enter event first. In either case, ignore the event. return; } mParent->mSerial = serial; const bool isDown = state != WL_KEYBOARD_KEY_STATE_RELEASED; if (isDown) mParent->mQDisplay->setLastInputDevice(mParent, serial, window); if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { #if QT_CONFIG(xkbcommon) if ((!mXkbKeymap || !mXkbState) && !createDefaultKeymap()) return; auto code = key + 8; // map to wl_keyboard::keymap_format::keymap_format_xkb_v1 xkb_keysym_t sym = xkb_state_key_get_one_sym(mXkbState.get(), code); Qt::KeyboardModifiers modifiers = QXkbCommon::modifiers(mXkbState.get(), sym); int qtkey = keysymToQtKey(sym, modifiers, mXkbState.get(), code); QString text = QXkbCommon::lookupString(mXkbState.get(), code); QEvent::Type type = isDown ? QEvent::KeyPress : QEvent::KeyRelease; handleKey(time, type, qtkey, modifiers, code, sym, mNativeModifiers, text); if (state == WL_KEYBOARD_KEY_STATE_PRESSED && xkb_keymap_key_repeats(mXkbKeymap.get(), code) && mRepeatRate > 0) { mRepeatKey.key = qtkey; mRepeatKey.code = code; mRepeatKey.time = time; mRepeatKey.text = text; mRepeatKey.nativeVirtualKey = sym; mRepeatTimer.setInterval(mRepeatDelay); mRepeatTimer.start(); } else if (mRepeatKey.code == code) { mRepeatTimer.stop(); } #else Q_UNUSED(time); Q_UNUSED(key); qCWarning(lcQpaWayland, "xkbcommon not available on this build, not performing key mapping"); return; #endif } else if (mKeymapFormat == WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP) { // raw scan code return; } } void QWaylandInputDevice::Keyboard::handleFocusDestroyed() { handleFocusLost(); } void QWaylandInputDevice::Keyboard::handleFocusLost() { mFocus = nullptr; mParent->mQDisplay->handleKeyboardFocusChanged(mParent); mRepeatTimer.stop(); } void QWaylandInputDevice::Keyboard::keyboard_modifiers(uint32_t serial, uint32_t mods_depressed, uint32_t mods_latched, uint32_t mods_locked, uint32_t group) { Q_UNUSED(serial); #if QT_CONFIG(xkbcommon) if (mXkbState) xkb_state_update_mask(mXkbState.get(), mods_depressed, mods_latched, mods_locked, 0, 0, group); mNativeModifiers = mods_depressed | mods_latched | mods_locked; #else Q_UNUSED(mods_depressed); Q_UNUSED(mods_latched); Q_UNUSED(mods_locked); Q_UNUSED(group); #endif } void QWaylandInputDevice::Keyboard::keyboard_repeat_info(int32_t rate, int32_t delay) { mRepeatRate = rate; mRepeatDelay = delay; } void QWaylandInputDevice::Touch::touch_down(uint32_t serial, uint32_t time, struct wl_surface *surface, int32_t id, wl_fixed_t x, wl_fixed_t y) { if (!surface) return; auto *window = QWaylandWindow::fromWlSurface(surface); if (!window) return; // Ignore foreign surfaces mParent->mTime = time; mParent->mSerial = serial; mFocus = window; mParent->mQDisplay->setLastInputDevice(mParent, serial, mFocus); QPointF position(wl_fixed_to_double(x), wl_fixed_to_double(y)); mParent->handleTouchPoint(id, QEventPoint::Pressed, position); } void QWaylandInputDevice::Touch::touch_up(uint32_t serial, uint32_t time, int32_t id) { Q_UNUSED(serial); mParent->mTime = time; mParent->handleTouchPoint(id, QEventPoint::Released); if (allTouchPointsReleased()) { mFocus = nullptr; // As of Weston 7.0.0 there is no touch_frame after the last touch_up // (i.e. when the last finger is released). To accommodate for this, issue a // touch_frame. This cannot hurt since it is safe to call the touch_frame // handler multiple times when there are no points left. // See: https://gitlab.freedesktop.org/wayland/weston/issues/44 // TODO: change logging category to lcQpaWaylandInput in newer versions. qCDebug(lcQpaWayland, "Generating fake frame event to work around Weston bug"); touch_frame(); } } void QWaylandInputDevice::Touch::touch_motion(uint32_t time, int32_t id, wl_fixed_t x, wl_fixed_t y) { QPointF position(wl_fixed_to_double(x), wl_fixed_to_double(y)); mParent->mTime = time; mParent->handleTouchPoint(id, QEventPoint::Updated, position); } void QWaylandInputDevice::Touch::touch_cancel() { mPendingTouchPoints.clear(); mFocus = nullptr; QWindowSystemInterface::handleTouchCancelEvent(nullptr, mParent->mTouchDevice); } void QWaylandInputDevice::handleTouchPoint(int id, QEventPoint::State state, const QPointF &surfacePosition) { auto end = mTouch->mPendingTouchPoints.end(); auto it = std::find_if(mTouch->mPendingTouchPoints.begin(), end, [id](const QWindowSystemInterface::TouchPoint &tp){ return tp.id == id; }); if (it == end) { it = mTouch->mPendingTouchPoints.insert(end, QWindowSystemInterface::TouchPoint()); it->id = id; } // If the touch points were up and down in same frame, send out frame right away else if ((it->state == QEventPoint::Pressed && state == QEventPoint::Released) || (it->state == QEventPoint::Released && state == QEventPoint::Pressed)) { mTouch->touch_frame(); it = mTouch->mPendingTouchPoints.insert(mTouch->mPendingTouchPoints.end(), QWindowSystemInterface::TouchPoint()); it->id = id; } QWindowSystemInterface::TouchPoint &tp = *it; // Only moved and pressed needs to update/set position if (state == QEventPoint::Updated || state == QEventPoint::Pressed) { // We need a global (screen) position. QWaylandWindow *win = mTouch->mFocus; //is it possible that mTouchFocus is null; if (!win && mPointer) win = mPointer->focusWindow(); if (!win && mKeyboard) win = mKeyboard->focusWindow(); if (!win || !win->window()) return; tp.area = QRectF(0, 0, 8, 8); const QPointF localPosition = win->mapFromWlSurface(surfacePosition); const QPointF globalPosition = win->mapToGlobalF(localPosition); tp.area.moveCenter(globalPosition); } // If the touch point was pressed earlier this frame, we don't want to overwrite its state. if (tp.state != QEventPoint::Pressed) tp.state = QEventPoint::State(state); tp.pressure = tp.state == QEventPoint::Released ? 0 : 1; } bool QWaylandInputDevice::Touch::allTouchPointsReleased() { for (const auto &tp : std::as_const(mPendingTouchPoints)) { if (tp.state != QEventPoint::Released) return false; } return true; } void QWaylandInputDevice::Touch::releasePoints() { if (mPendingTouchPoints.empty()) return; for (QWindowSystemInterface::TouchPoint &tp : mPendingTouchPoints) tp.state = QEventPoint::Released; touch_frame(); } void QWaylandInputDevice::Touch::touch_frame() { // TODO: early return if no events? QWindow *window = mFocus ? mFocus->window() : nullptr; if (mFocus) { // Returns a reference to the last item in the list. The list must not be empty. // If the list can be empty, call isEmpty() before calling this function. // See: https://doc.qt.io/qt-5.15/qlist.html#last if (mPendingTouchPoints.empty()) return; const QWindowSystemInterface::TouchPoint &tp = mPendingTouchPoints.constLast(); // When the touch event is received, the global pos is calculated with the margins // in mind. Now we need to adjust again to get the correct local pos back. QMargins margins = mFocus->clientSideMargins(); QPoint p = tp.area.center().toPoint(); QPointF localPos(mFocus->mapFromGlobal(p) + QPoint(margins.left(), margins.top())); if (mFocus->touchDragDecoration(mParent, localPos, tp.area.center(), tp.state, mParent->modifiers())) return; } QWindowSystemInterface::handleTouchEvent(window, mParent->mTime, mParent->mTouchDevice, mPendingTouchPoints, mParent->modifiers()); // Prepare state for next frame const auto prevTouchPoints = mPendingTouchPoints; mPendingTouchPoints.clear(); for (const auto &prevPoint: prevTouchPoints) { // All non-released touch points should be part of the next touch event if (prevPoint.state != QEventPoint::Released) { QWindowSystemInterface::TouchPoint tp = prevPoint; tp.state = QEventPoint::Stationary; // ... as stationary (unless proven otherwise) mPendingTouchPoints.append(tp); } } } } QT_END_NAMESPACE #include "moc_qwaylandinputdevice_p.cpp"