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();