From 8920e96e5d75dd907ddeef876a77803ef6c794b5 Mon Sep 17 00:00:00 2001 From: LFRon Date: Fri, 26 Jun 2026 21:30:32 +0800 Subject: [PATCH] fix: handle XWayland transient popups and dialogs XWayland override-redirect transients and mapped X11 configure flows can arrive before treeland has a wrapper/container ready. Focus or pointer transitions during the opening click also let wlroots reset pointer button state, so dialogs and menus from apps such as WeChat and VirtualBox could close immediately after appearing. Forward wlroots focus_in/grab_focus through qwlroots and waylib, defer override-redirect focus until the surface is mapped, and apply it only when wlroots reports the surface wants keyboard focus. Defer pointer enter/clear while buttons are held so the click that opens a popup is not converted into an unintended release/activation. Wait for parent association before reparenting XWayland children, accept mapped ConfigureRequest sizes for managed XWayland windows, guard recursive resize feedback, and sync the accepted content size after X11 configure to avoid transient main-window artifacts. Keep the remaining debug output focused on the core deferred/applied/dropped decisions. --- qwlroots/src/types/qwxwaylandsurface.h | 2 + src/core/shellhandler.cpp | 256 ++++++++++++++++-- src/core/shellhandler.h | 8 +- src/surface/surfacewrapper.cpp | 76 +++++- src/surface/surfacewrapper.h | 3 +- waylib/src/server/kernel/wseat.cpp | 137 +++++++++- waylib/src/server/protocols/wxwayland.cpp | 1 + .../src/server/protocols/wxwaylandsurface.cpp | 33 ++- .../src/server/protocols/wxwaylandsurface.h | 3 + .../server/qtquick/wxwaylandsurfaceitem.cpp | 21 ++ .../src/server/qtquick/wxwaylandsurfaceitem.h | 3 + 11 files changed, 516 insertions(+), 27 deletions(-) diff --git a/qwlroots/src/types/qwxwaylandsurface.h b/qwlroots/src/types/qwxwaylandsurface.h index 6755b104f..883224311 100644 --- a/qwlroots/src/types/qwxwaylandsurface.h +++ b/qwlroots/src/types/qwxwaylandsurface.h @@ -38,6 +38,8 @@ class QW_CLASS_OBJECT(xwayland_surface) QW_SIGNAL(set_decorations) QW_SIGNAL(set_override_redirect) QW_SIGNAL(set_geometry) + QW_SIGNAL(focus_in) + QW_SIGNAL(grab_focus) QW_SIGNAL(ping_timeout) QW_SIGNAL(associate) QW_SIGNAL(dissociate) diff --git a/src/core/shellhandler.cpp b/src/core/shellhandler.cpp index 72521e125..53330e157 100644 --- a/src/core/shellhandler.cpp +++ b/src/core/shellhandler.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -55,6 +56,16 @@ WAYLIB_SERVER_USE_NAMESPACE #define TREELAND_XDG_SHELL_VERSION 5 +namespace { + +bool overrideRedirectWantsKeyboardFocus(WXWaylandSurface *surface) +{ + return surface && !surface->isInvalidated() && surface->handle() && surface->isBypassManager() + && surface->hasCapability(WToplevelSurface::Capability::Focus); +} + +} // namespace + ShellHandler::ShellHandler(RootSurfaceContainer *rootContainer, WServer *server) : m_rootSurfaceContainer(rootContainer) , m_backgroundContainer(new LayerSurfaceContainer(rootContainer)) @@ -91,32 +102,43 @@ ShellHandler::ShellHandler(RootSurfaceContainer *rootContainer, WServer *server) void ShellHandler::updateWrapperContainer(SurfaceWrapper *wrapper, WSurface *parentSurface) { + if (!wrapper) + return; + + if (!wrapper->shellSurface() || !wrapper->surface()) + return; + if (wrapper->parentSurface()) wrapper->parentSurface()->removeSubSurface(wrapper); auto oldContainer = wrapper->container(); + SurfaceWrapper *parentWrapper = nullptr; + SurfaceContainer *parentContainer = nullptr; if (parentSurface) { - auto parentWrapper = m_rootSurfaceContainer->getSurface(parentSurface); - auto parentContainer = qobject_cast(parentWrapper->container()); - parentWrapper->addSubSurface(wrapper); - if (oldContainer != parentContainer) { - if (oldContainer) - oldContainer->removeSurface(wrapper); - if (auto ws = qobject_cast(parentContainer)) - ws->addSurface(wrapper, parentWrapper->workspaceId()); - else - parentContainer->addSurface(wrapper); - } - } else { - if (oldContainer) { - if (qobject_cast(oldContainer) == nullptr) { - oldContainer->removeSurface(wrapper); - m_workspace->addSurface(wrapper); + parentWrapper = m_rootSurfaceContainer->getSurface(parentSurface); + parentContainer = parentWrapper ? parentWrapper->container() : nullptr; + if (parentWrapper && parentWrapper != wrapper && parentContainer) { + parentWrapper->addSubSurface(wrapper); + if (oldContainer != parentContainer) { + if (oldContainer) + oldContainer->removeSurface(wrapper); + if (auto ws = qobject_cast(parentContainer)) + ws->addSurface(wrapper, parentWrapper->workspaceId()); + else + parentContainer->addSurface(wrapper); } - // else do nothing, already in workspace - } else { + return; + } + } + + if (oldContainer) { + if (qobject_cast(oldContainer) == nullptr) { + oldContainer->removeSurface(wrapper); m_workspace->addSurface(wrapper); } + // else do nothing, already in workspace + } else { + m_workspace->addSurface(wrapper); } } @@ -578,6 +600,21 @@ void ShellHandler::onXdgPopupSurfaceRemoved(WXdgPopupSurface *surface) void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) { + connect(surface, &QObject::destroyed, this, [this, surface] { + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + }); + + surface->safeConnect(&WXWaylandSurface::focusIn, + this, + [this, surface = QPointer(surface)] { + handleXWaylandOverrideRedirectFocus(surface.data()); + }); + surface->safeConnect(&WXWaylandSurface::grabFocus, + this, + [this, surface = QPointer(surface)] { + handleXWaylandOverrideRedirectFocus(surface.data()); + }); + surface->safeConnect(&WXWaylandSurface::associated, this, [this, surface = QPointer(surface)] { @@ -625,6 +662,7 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) surface->safeConnect(&WXWaylandSurface::aboutToDissociate, this, [this, surface] { auto wrapper = m_rootSurfaceContainer->getSurface(surface); qCDebug(lcTlShell) << "WXWayland::aboutToDissociate" << surface << wrapper; + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); // Cancel pending async property fetch for this surface. auto *xwayland = surface->xwayland(); @@ -641,6 +679,27 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) } return; // never created } + if (surface->isBypassManager()) { + auto *seatContainer = m_rootSurfaceContainer->getSeatContainerOrDefault(nullptr); + if (seatContainer && seatContainer->keyboardFocusSurface() == wrapper) { + auto isKeyboardFocusable = [](SurfaceWrapper *candidate) { + return candidate && candidate->surface() && candidate->surface()->mapped() + && candidate->hasCapability(WToplevelSurface::Capability::Focus) + && candidate->acceptKeyboardFocus(); + }; + + auto *fallback = m_rootSurfaceContainer->getSurface(surface->parentSurface()); + if (!isKeyboardFocusable(fallback)) { + fallback = m_workspace && m_workspace->current() + ? m_workspace->current()->latestActiveSurface() + : nullptr; + } + if (fallback == wrapper || !isKeyboardFocusable(fallback)) + fallback = nullptr; + + seatContainer->setKeyboardFocusSurface(fallback); + } + } // Persist XWayland window size if (m_windowConfigStore && !wrapper->appId().isEmpty()) { QSizeF sz = wrapper->normalGeometry().size(); @@ -657,6 +716,101 @@ void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface) }); } +void ShellHandler::handleXWaylandOverrideRedirectFocus(WXWaylandSurface *surface) +{ + if (!surface) + return; + + if (!surface->isBypassManager()) + return; + + auto *wrapper = m_rootSurfaceContainer->getSurface(surface); + auto *waylandSurface = surface->surface(); + const bool surfaceMapped = waylandSurface && waylandSurface->mapped(); + const bool wantsKeyboardFocus = overrideRedirectWantsKeyboardFocus(surface); + + if (!wantsKeyboardFocus) { + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + qCDebug(lcTlXwayland) + << "XWayland override-redirect focus ignored because wlroots reports it should not take keyboard focus" + << "surface" << surface + << "windowTypes" << surface->windowTypes().toInt(); + return; + } + + if (!surfaceMapped) { + m_pendingXWaylandOverrideRedirectFocuses.insert(surface); + qCDebug(lcTlXwayland) + << "XWayland override-redirect focus deferred" + << "surface" << surface; + return; + } + + focusXWaylandOverrideRedirectSurface(surface, wrapper); +} + +void ShellHandler::applyPendingXWaylandOverrideRedirectFocus(WXWaylandSurface *surface, + SurfaceWrapper *wrapper) +{ + if (!m_pendingXWaylandOverrideRedirectFocuses.contains(surface)) + return; + + if (!surface || !surface->isBypassManager()) { + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + return; + } + + if (!overrideRedirectWantsKeyboardFocus(surface)) { + qCDebug(lcTlXwayland) + << "XWayland pending override-redirect focus dropped because wlroots reports it should not take keyboard focus" + << "surface" << surface + << "windowTypes" << surface->windowTypes().toInt(); + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + return; + } + + auto *waylandSurface = surface->surface(); + if (!waylandSurface || !waylandSurface->mapped()) + return; + + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + + qCDebug(lcTlXwayland) + << "XWayland applying deferred override-redirect focus" + << "surface" << surface; + + focusXWaylandOverrideRedirectSurface(surface, wrapper); +} + +void ShellHandler::focusXWaylandOverrideRedirectSurface(WXWaylandSurface *surface, + SurfaceWrapper *wrapper) +{ + if (!surface || !surface->isBypassManager() || !surface->surface() + || !surface->surface()->mapped()) { + return; + } + + if (!overrideRedirectWantsKeyboardFocus(surface)) { + m_pendingXWaylandOverrideRedirectFocuses.remove(surface); + qCDebug(lcTlXwayland) + << "XWayland override-redirect keyboard focus skipped because wlroots reports it should not take keyboard focus" + << "surface" << surface + << "windowTypes" << surface->windowTypes().toInt(); + return; + } + + auto *targetWrapper = wrapper ? wrapper : m_rootSurfaceContainer->getSurface(surface); + if (auto *seatContainer = m_rootSurfaceContainer->getSeatContainerOrDefault(nullptr); + seatContainer && targetWrapper) { + seatContainer->setKeyboardFocusSurface(targetWrapper); + return; + } + + if (auto *seat = m_rootSurfaceContainer->getDefaultSeat()) { + seat->setKeyboardFocusSurface(surface->surface()); + } +} + void ShellHandler::fetchInitialProperties(WXWaylandSurface *surface, const QString &appId) { auto *xwayland = surface->xwayland(); @@ -749,11 +903,34 @@ void ShellHandler::ensureXwaylandWrapper(WXWaylandSurface *surface, const QStrin } // Initialize wrapper - auto updateSurfaceWithParentContainer = [this, wrapper, surface] { - updateWrapperContainer(wrapper, surface->parentSurface()); + auto updateSurfaceWithParentContainer = + [self = QPointer(this), + wrapper = QPointer(wrapper), + surface = QPointer(surface)] { + auto *handler = self.data(); + auto *wrapperPtr = wrapper.data(); + auto *surfacePtr = surface.data(); + if (!handler || !wrapperPtr || !surfacePtr) + return; + + if (wrapperPtr->shellSurface() != surfacePtr) { + return; + } + + if (!surfacePtr->surface()) { + return; + } + + auto parentXWaylandSurface = surfacePtr->parentXWaylandSurface(); + auto parentSurface = surfacePtr->parentSurface(); + if (parentXWaylandSurface && !parentSurface) { + return; + } + + handler->updateWrapperContainer(wrapperPtr, parentSurface); }; surface->safeConnect(&WXWaylandSurface::parentSurfaceChanged, - this, + wrapper, updateSurfaceWithParentContainer); updateSurfaceWithParentContainer(); Q_ASSERT(wrapper->parentItem()); @@ -764,6 +941,43 @@ void ShellHandler::ensureXwaylandWrapper(WXWaylandSurface *surface, const QStrin setupSurfaceActiveWatcher(wrapper); registerSurfaceToForeignToplevel(wrapper); } + if (isNewWrapper && wrapper->surface()) { + auto *seat = m_rootSurfaceContainer->getDefaultSeat(); + auto *seatContainer = seat ? m_rootSurfaceContainer->getSeatContainer(seat) : nullptr; + const bool canSyncPreWrapperKeyboardFocus = + !surface->isBypassManager() || overrideRedirectWantsKeyboardFocus(surface); + if (seat && seatContainer && canSyncPreWrapperKeyboardFocus + && seat->keyboardFocusSurface() == wrapper->surface() + && seatContainer->keyboardFocusSurface() != wrapper) { + seatContainer->setKeyboardFocusSurface(wrapper); + } else if (!canSyncPreWrapperKeyboardFocus && seat + && seat->keyboardFocusSurface() == wrapper->surface()) { + qCDebug(lcTlXwayland) + << "XWayland pre-wrapper keyboard focus sync skipped because wlroots reports it should not take keyboard focus" + << "surface" << surface + << "windowTypes" << surface->windowTypes().toInt(); + } + wrapper->surface()->safeConnect(&WSurface::mappedChanged, + wrapper, + [this, + surface = QPointer(surface), + wrapper = QPointer(wrapper)] { + auto *surfacePtr = surface.data(); + auto *wrapperPtr = wrapper.data(); + if (!surfacePtr || !wrapperPtr || !surfacePtr->surface() + || !surfacePtr->surface()->mapped()) { + return; + } + applyPendingXWaylandOverrideRedirectFocus(surfacePtr, + wrapperPtr); + }); + } + if (isNewWrapper && wrapper->surface() && wrapper->surface()->mapped()) { + wrapper->onMappedChanged(); + } + if (wrapper->surface() && wrapper->surface()->mapped()) { + applyPendingXWaylandOverrideRedirectFocus(surface, wrapper); + } Q_EMIT surfaceWrapperAdded(wrapper); } diff --git a/src/core/shellhandler.h b/src/core/shellhandler.h index 362cb8772..e808e744b 100644 --- a/src/core/shellhandler.h +++ b/src/core/shellhandler.h @@ -11,7 +11,7 @@ #include -#include +#include #include #include #include @@ -168,6 +168,11 @@ private Q_SLOTS: void onInitialPropertiesReady(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface, const QString &appId, const QMap &result); + void handleXWaylandOverrideRedirectFocus(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface); + void applyPendingXWaylandOverrideRedirectFocus(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface, + SurfaceWrapper *wrapper); + void focusXWaylandOverrideRedirectSurface(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface, + SurfaceWrapper *wrapper); WAYLIB_SERVER_NAMESPACE::WXdgShell *m_xdgShell = nullptr; WAYLIB_SERVER_NAMESPACE::WLayerShell *m_layerShell = nullptr; @@ -201,6 +206,7 @@ private Q_SLOTS: // Pending toplevel surfaces (XDG or XWayland) awaiting async AppId resolve; callbacks continue // only if the pointer remains in this list QList m_pendingAppIdResolveToplevels; + QSet m_pendingXWaylandOverrideRedirectFocuses; // New protocol based app id resolver (optional, may be null if module not loaded) AppIdResolverManager *m_appIdResolverManager = nullptr; WindowConfigStore *m_windowConfigStore = nullptr; diff --git a/src/surface/surfacewrapper.cpp b/src/surface/surfacewrapper.cpp index 68cbc78f7..d61f80ffe 100644 --- a/src/surface/surfacewrapper.cpp +++ b/src/surface/surfacewrapper.cpp @@ -27,6 +27,7 @@ #include #include +#include #include #define OPEN_ANIMATION 1 @@ -397,6 +398,79 @@ void SurfaceWrapper::setup() moveNormalGeometryInOutput(xwaylandSurfaceItem->implicitPosition()); }); + auto applyMappedConfigureRequestSize = + [this, xwaylandSurface, xwaylandSurfaceItem](WXWaylandSurface::ConfigureFlags flags) { + if (m_wrapperAboutToRemove || !m_surfaceItem || !xwaylandSurfaceItem + || xwaylandSurface->isBypassManager()) + return; + if (!flags.testAnyFlags(WXWaylandSurface::ConfigureFlag::XCB_CONFIG_WINDOW_SIZE)) + return; + + updateSurfaceSizeRatio(); + + const QRect requestGeometry = xwaylandSurface->requestConfigureGeometry(); + const QSize requestedBufferSize = requestGeometry.size(); + if (!requestedBufferSize.isValid() || requestedBufferSize.isEmpty()) + return; + const QSize currentBufferSize = xwaylandSurface->geometry().size(); + + const qreal surfaceSizeRatio = xwaylandSurfaceItem->surfaceSizeRatio(); + if (surfaceSizeRatio <= 0.0) + return; + + const QSizeF requestedSceneSize( + requestedBufferSize.width() / surfaceSizeRatio + + xwaylandSurfaceItem->leftPadding() + + xwaylandSurfaceItem->rightPadding(), + requestedBufferSize.height() / surfaceSizeRatio + + xwaylandSurfaceItem->topPadding() + + xwaylandSurfaceItem->bottomPadding()); + + const bool sceneSizeUnchanged = + qFuzzyCompare(width() + 1.0, requestedSceneSize.width() + 1.0) + && qFuzzyCompare(height() + 1.0, requestedSceneSize.height() + 1.0); + const bool surfaceSizeUnchanged = currentBufferSize == requestedBufferSize; + if (sceneSizeUnchanged && surfaceSizeUnchanged) + return; + + QRectF requestedNormalGeometry = normalGeometry(); + if (!requestedNormalGeometry.isValid() || requestedNormalGeometry.isEmpty()) + requestedNormalGeometry = QRectF(position(), size()); + requestedNormalGeometry.setSize(requestedSceneSize); + + setNormalGeometry(requestedNormalGeometry); + if (isNormal()) { + if (m_geometryAnimation) { + m_geometryAnimation->setProperty("toGeometry", requestedNormalGeometry); + } else { + { + QScopedValueRollback guard(m_applyingXwaylandConfigureRequestSize, true); + setSize(requestedSceneSize); + } + + if (!surfaceSizeUnchanged) { + QRect configureGeometry = requestGeometry; + if (!m_xwaylandPositionFromSurface) + configureGeometry.moveTopLeft(xwaylandSurface->geometry().topLeft()); + xwaylandSurfaceItem->configureSurfaceGeometry(configureGeometry); + } + } + } else if (m_pendingState == State::Normal && m_geometryAnimation) { + m_geometryAnimation->setProperty("toGeometry", requestedNormalGeometry); + } + }; + + connect(xwaylandSurface, + &WXWaylandSurface::requestConfigure, + this, + [applyMappedConfigureRequestSize](const QRect &, + WXWaylandSurface::ConfigureFlags flags) { + // SurfaceWrapper keeps XWayland items in ManualResize mode, so mapped X11 + // ConfigureRequest sizes need to be accepted here instead of relying on + // WXWaylandSurfaceItem implicit-size updates. + applyMappedConfigureRequestSize(flags); + }); + connect(this, &QQuickItem::xChanged, xwaylandSurface, [this, xwaylandSurfaceItem]() { xwaylandSurfaceItem->moveTo(position(), !m_xwaylandPositionFromSurface); }); @@ -1240,7 +1314,7 @@ void SurfaceWrapper::geometryChange(const QRectF &newGeo, const QRectF &oldGeome setNormalGeometry(newGeometry); } - if (widthValid() && heightValid()) { + if (widthValid() && heightValid() && !m_applyingXwaylandConfigureRequestSize) { resize(newGeometry.size()); } diff --git a/src/surface/surfacewrapper.h b/src/surface/surfacewrapper.h index c536376e6..2de9483e5 100644 --- a/src/surface/surfacewrapper.h +++ b/src/surface/surfacewrapper.h @@ -436,7 +436,7 @@ public Q_SLOTS: Type m_type; QPointer m_ownsOutput; QPointF m_positionInOwnsOutput; - SurfaceWrapper::State m_pendingState; + SurfaceWrapper::State m_pendingState = State::Normal; QRectF m_pendingGeometry; QPointer m_windowAnimation; QPointer m_minimizeAnimation; @@ -488,6 +488,7 @@ public Q_SLOTS: bool m_socketEnabled{ false }; bool m_windowAnimationEnabled{ true }; bool m_acceptKeyboardFocus{ true }; + bool m_applyingXwaylandConfigureRequestSize{ false }; const QString m_appId; }; diff --git a/waylib/src/server/kernel/wseat.cpp b/waylib/src/server/kernel/wseat.cpp index 9353945d1..c5b6dd366 100644 --- a/waylib/src/server/kernel/wseat.cpp +++ b/waylib/src/server/kernel/wseat.cpp @@ -132,6 +132,117 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate return nativeHandle()->keyboard_state.focused_surface; } + inline bool pointerButtonsPressed() const { + return nativeHandle()->pointer_state.button_count > 0; + } + + inline wlr_surface *nativeSurfaceFor(WSurface *surface) const { + return surface && surface->handle() ? surface->handle()->handle() : nullptr; + } + + inline bool shouldDeferPointerFocusChange(wlr_surface *nextFocus) const { + return pointerButtonsPressed() + && !handle()->pointer_has_grab() + && pointerFocusSurface() != nextFocus; + } + + inline bool shouldDeferPointerEnter(WSurface *surface) const { + return shouldDeferPointerFocusChange(nativeSurfaceFor(surface)); + } + + inline bool shouldDeferPointerClear() const { + return shouldDeferPointerFocusChange(nullptr); + } + + inline void clearDeferredPointerFocus() { + pendingPointerEnter = false; + pendingPointerClear = false; + pendingPointerEnterHadEventObject = false; + pendingPointerEnterSurface = nullptr; + pendingPointerEnterEventObject = nullptr; + pendingPointerEnterPosition = QPointF(); + } + + inline void deferPointerEnter(WSurface *surface, QObject *eventObject, const QPointF &position) { + pendingPointerEnter = true; + pendingPointerClear = false; + pendingPointerEnterHadEventObject = eventObject != nullptr; + pendingPointerEnterSurface = surface; + pendingPointerEnterEventObject = eventObject; + pendingPointerEnterPosition = position; + + qCDebug(lcWlPointer) + << "Seat pointer enter deferred until buttons are released" + << "target" << surface + << "currentPointerFocus" << pointerFocusSurface() + << "buttonCount" << nativeHandle()->pointer_state.button_count; + } + + inline void deferPointerClear() { + if (!pendingPointerEnter) + pendingPointerClear = true; + + qCDebug(lcWlPointer) + << "Seat pointer clear deferred until buttons are released" + << "currentPointerFocus" << pointerFocusSurface() + << "hasPendingEnter" << pendingPointerEnter + << "buttonCount" << nativeHandle()->pointer_state.button_count; + } + + inline void applyDeferredPointerFocus(uint32_t timestamp) { + if (!pendingPointerEnter && !pendingPointerClear) + return; + + if (pointerButtonsPressed()) + return; + + if (handle()->pointer_has_grab()) { + qCDebug(lcWlPointer) + << "Seat deferred pointer focus dropped because wlroots grab is active" + << "buttonCount" << nativeHandle()->pointer_state.button_count + << "pointerFocus" << pointerFocusSurface(); + clearDeferredPointerFocus(); + return; + } + + const bool applyEnter = pendingPointerEnter; + const bool applyClear = pendingPointerClear; + QPointer surface = pendingPointerEnterSurface; + QPointer eventObject = pendingPointerEnterEventObject; + const bool hadEventObject = pendingPointerEnterHadEventObject; + const QPointF position = pendingPointerEnterPosition; + clearDeferredPointerFocus(); + + if (applyEnter) { + if (!surface || !surface->mapped() || !surface->handle() + || (hadEventObject && !eventObject)) { + qCDebug(lcWlPointer) + << "Seat deferred pointer enter dropped because target is no longer valid" + << "target" << surface.data() + << "hadEventObject" << hadEventObject; + return; + } + + qCDebug(lcWlPointer) + << "Seat deferred pointer enter applying" + << "target" << surface.data() + << "timestamp" << timestamp + << "currentPointerFocus" << pointerFocusSurface(); + + if (doEnter(surface.data(), eventObject.data(), position)) + doNotifyMotion(surface.data(), eventObject.data(), position, timestamp); + return; + } + + if (applyClear && pointerFocusSurface()) { + qCDebug(lcWlPointer) + << "Seat deferred pointer clear applying" + << "timestamp" << timestamp + << "currentPointerFocus" << pointerFocusSurface(); + doClearPointerFocus(); + } + } + inline bool doNotifyMotion(WSurface *target, QObject *eventObject, QPointF localPos, uint32_t timestamp) { if (target) { if (pointerFocusSurface()) { @@ -150,6 +261,8 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate } inline bool doNotifyButton(uint32_t button, wl_pointer_button_state state, uint32_t timestamp) { handle()->pointer_notify_button(timestamp, button, state); + if (state == WL_POINTER_BUTTON_STATE_RELEASED) + applyDeferredPointerFocus(timestamp); return true; } static inline wl_pointer_axis fromQtHorizontal(Qt::Orientation o) { @@ -170,12 +283,19 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate handle()->pointer_notify_frame(); } inline bool doEnter(WSurface *surface, QObject *eventObject, const QPointF &position) { + if (shouldDeferPointerEnter(surface)) { + deferPointerEnter(surface, eventObject, position); + return true; + } + // doEnter be called from QEvent::HoverEnter is normal, // but doNotifyMotion will call doEnter too, // so should compare pointerFocusEventObject and eventObject early if (pointerFocusEventObject == eventObject) { + clearDeferredPointerFocus(); return true; } + clearDeferredPointerFocus(); auto tmp = oldPointerFocusSurface; oldPointerFocusSurface = handle()->handle()->pointer_state.focused_surface; handle()->pointer_notify_enter(surface->handle()->handle(), position.x(), position.y()); @@ -205,6 +325,7 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate return true; } inline void doClearPointerFocus() { + clearDeferredPointerFocus(); pointerFocusEventObject.clear(); handle()->pointer_notify_clear_focus(); Q_ASSERT(!handle()->handle()->pointer_state.focused_surface); @@ -368,6 +489,13 @@ class Q_DECL_HIDDEN WSeatPrivate : public WWrapObjectPrivate QMetaObject::Connection onEventObjectDestroy; wlr_surface *oldPointerFocusSurface = nullptr; + bool pendingPointerEnter = false; + bool pendingPointerClear = false; + bool pendingPointerEnterHadEventObject = false; + QPointer pendingPointerEnterSurface; + QPointer pendingPointerEnterEventObject; + QPointF pendingPointerEnterPosition; + bool gestureActive = false; int gestureFingers = 0; qreal lastScale = 1.0; @@ -903,6 +1031,10 @@ bool WSeat::sendEvent(WSurface *target, QObject *shellObject, QObject *eventObje break; auto nativeTarget = target->handle()->handle(); Q_ASSERT(!currentFocus || d->oldPointerFocusSurface == nativeTarget || currentFocus == nativeTarget); + if (d->shouldDeferPointerClear()) { + d->deferPointerClear(); + break; + } d->doClearPointerFocus(); break; } @@ -926,8 +1058,11 @@ bool WSeat::sendEvent(WSurface *target, QObject *shellObject, QObject *eventObje if (d->pointerFocusEventObject) { // received HoverEnter event of next eventObject before HoverLeave event of last eventObject, // so we should check the eventObject is still the same, if not, we should ignore this event - if (d->pointerFocusEventObject != eventObject) + if (d->pointerFocusEventObject != eventObject) { + if (d->shouldDeferPointerEnter(target)) + d->deferPointerEnter(target, eventObject, e->position()); break; + } } d->doNotifyMotion(target, eventObject, e->position(), e->timestamp()); break; diff --git a/waylib/src/server/protocols/wxwayland.cpp b/waylib/src/server/protocols/wxwayland.cpp index a1c79b79c..5479c4bf0 100644 --- a/waylib/src/server/protocols/wxwayland.cpp +++ b/waylib/src/server/protocols/wxwayland.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include diff --git a/waylib/src/server/protocols/wxwaylandsurface.cpp b/waylib/src/server/protocols/wxwaylandsurface.cpp index 3882b3c2d..cc2595705 100644 --- a/waylib/src/server/protocols/wxwaylandsurface.cpp +++ b/waylib/src/server/protocols/wxwaylandsurface.cpp @@ -15,7 +15,9 @@ #include #include +#include #include +#include #include @@ -71,6 +73,7 @@ class Q_DECL_HIDDEN WXWaylandSurfacePrivate : public WToplevelSurfacePrivate QList children; QPointer parent; + QMetaObject::Connection parentSurfaceChangedConnection; QRect lastRequestConfigureGeometry; WXWaylandSurface::ConfigureFlags lastRequestConfigureFlags = {0}; WXWaylandSurface::WindowTypes windowTypes = {0}; @@ -87,6 +90,8 @@ void WXWaylandSurfacePrivate::instantRelease() W_Q(WXWaylandSurface); handle()->set_data(nullptr, nullptr); handle()->disconnect(q); + QObject::disconnect(parentSurfaceChangedConnection); + parentSurfaceChangedConnection = {}; if (!surface) return; @@ -117,6 +122,12 @@ void WXWaylandSurfacePrivate::init() updateParent(); }); QObject::connect(handle(), &qw_xwayland_surface::notify_request_activate, q, &WXWaylandSurface::requestActivate); + QObject::connect(handle(), &qw_xwayland_surface::notify_focus_in, q, [q] { + Q_EMIT q->focusIn(); + }); + QObject::connect(handle(), &qw_xwayland_surface::notify_grab_focus, q, [q] { + Q_EMIT q->grabFocus(); + }); QObject::connect(handle(), &qw_xwayland_surface::notify_request_configure, q, [this, q] (wlr_xwayland_surface_configure_event *event) { lastRequestConfigureGeometry = QRect(event->x, event->y, event->width, event->height); @@ -239,17 +250,29 @@ void WXWaylandSurfacePrivate::updateParent() if (parent == newParent) return; + W_Q(WXWaylandSurface); + + const auto oldParentSurface = parent ? parent->surface() : nullptr; const bool hasParentChanged = (parent == nullptr) != (newParent == nullptr); // QPointer handles destroyed wrappers; isInvalidated() prevents accessing destroyed wlroots objects. if (parent && !parent->isInvalidated()) parent->d_func()->updateChildren(); + QObject::disconnect(parentSurfaceChangedConnection); + parentSurfaceChangedConnection = {}; parent = newParent; - if (parent) + if (parent) { parent->d_func()->updateChildren(); + parentSurfaceChangedConnection = QObject::connect(parent, + &WToplevelSurface::surfaceChanged, + q, + &WToplevelSurface::parentSurfaceChanged); + } - W_Q(WXWaylandSurface); + const auto newParentSurface = parent ? parent->surface() : nullptr; Q_EMIT q->parentXWaylandSurfaceChanged(); + if (oldParentSurface != newParentSurface) + Q_EMIT q->parentSurfaceChanged(); if (hasParentChanged) Q_EMIT q->isToplevelChanged(); @@ -340,6 +363,12 @@ WSurface *WXWaylandSurface::surface() const return d->surface; } +WSurface *WXWaylandSurface::parentSurface() const +{ + auto parent = parentXWaylandSurface(); + return parent ? parent->surface() : nullptr; +} + qw_xwayland_surface *WXWaylandSurface::handle() const { W_DC(WXWaylandSurface); diff --git a/waylib/src/server/protocols/wxwaylandsurface.h b/waylib/src/server/protocols/wxwaylandsurface.h index 4d1f32c99..d9749801e 100644 --- a/waylib/src/server/protocols/wxwaylandsurface.h +++ b/waylib/src/server/protocols/wxwaylandsurface.h @@ -84,6 +84,7 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurface : public WToplevelSurface static WXWaylandSurface *fromSurface(WSurface *surface); WSurface *surface() const override; + WSurface *parentSurface() const override; QW_NAMESPACE::qw_xwayland_surface *handle() const; WXWaylandSurface *parentXWaylandSurface() const; WXWayland *xwayland() const; @@ -144,6 +145,8 @@ public Q_SLOTS: void requestConfigure(QRect geometry, ConfigureFlags flags); void requestActivate(); + void focusIn(); + void grabFocus(); }; WAYLIB_SERVER_END_NAMESPACE diff --git a/waylib/src/server/qtquick/wxwaylandsurfaceitem.cpp b/waylib/src/server/qtquick/wxwaylandsurfaceitem.cpp index b13e94285..e7f1348b5 100644 --- a/waylib/src/server/qtquick/wxwaylandsurfaceitem.cpp +++ b/waylib/src/server/qtquick/wxwaylandsurfaceitem.cpp @@ -6,6 +6,8 @@ #include "wxwaylandsurface.h" #include "wxwayland.h" +#include + QW_USE_NAMESPACE WAYLIB_SERVER_BEGIN_NAMESPACE @@ -34,8 +36,21 @@ void WXWaylandSurfaceItemPrivate::configureSurface(const QRect &newGeometry) Q_Q(WXWaylandSurfaceItem); if (!q->isVisible()) return; + + const QSizeF oldContentSize = contentContainer ? contentContainer->size() : QSizeF(); q->xwaylandSurface()->configure(newGeometry); q->updateSurfaceState(); + + if (!contentContainer || !surfaceState) + return; + + const QSizeF newContentSize = surfaceState->contentSize; + if (oldContentSize != newContentSize) { + contentContainer->setSize(newContentSize); + if (surface) + beforeRequestResizeSurfaceStateSeq = surface->handle()->handle()->pending.seq; + } + updateContentPosition(); } QSize WXWaylandSurfaceItemPrivate::expectSurfaceSize() const @@ -185,6 +200,12 @@ QPointF WXWaylandSurfaceItem::implicitPosition() const return QPointF(epos) / ssr - QPointF(leftPadding(), topPadding()); } +void WXWaylandSurfaceItem::configureSurfaceGeometry(const QRect &geometry) +{ + Q_D(WXWaylandSurfaceItem); + d->configureSurface(geometry); +} + void WXWaylandSurfaceItem::onSurfaceCommit() { diff --git a/waylib/src/server/qtquick/wxwaylandsurfaceitem.h b/waylib/src/server/qtquick/wxwaylandsurfaceitem.h index 95abdf7bf..f93899103 100644 --- a/waylib/src/server/qtquick/wxwaylandsurfaceitem.h +++ b/waylib/src/server/qtquick/wxwaylandsurfaceitem.h @@ -7,6 +7,7 @@ #include #include +#include WAYLIB_SERVER_BEGIN_NAMESPACE @@ -37,6 +38,8 @@ class WAYLIB_SERVER_EXPORT WXWaylandSurfaceItem : public WSurfaceItem void moveTo(const QPointF &pos, bool configSurface); QPointF implicitPosition() const; + // Applies compositor-accepted X11 geometry and refreshes item state immediately. + void configureSurfaceGeometry(const QRect &geometry); Q_SIGNALS: void implicitPositionChanged();