Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions qwlroots/src/types/qwxwaylandsurface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
256 changes: 235 additions & 21 deletions src/core/shellhandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@
#include <winputmethodhelper.h>
#include <winputpopupsurface.h>
#include <wlayershell.h>
#include <wlayersurface.h>

Check warning on line 31 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wlayersurface.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <woutputrenderwindow.h>

Check warning on line 32 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <woutputrenderwindow.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wserver.h>

Check warning on line 33 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wserver.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wseat.h>

Check warning on line 34 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wseat.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wxdgpopupsurface.h>

Check warning on line 35 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wxdgpopupsurface.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wxdgshell.h>

Check warning on line 36 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wxdgshell.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wxdgtoplevelsurface.h>

Check warning on line 37 in src/core/shellhandler.cpp

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <wxdgtoplevelsurface.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <wxwayland.h>
#include <wxwaylandsurface.h>
#include <wxwaylandsurfaceitem.h>
Expand All @@ -55,6 +56,16 @@

#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))
Expand Down Expand Up @@ -91,32 +102,43 @@

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<SurfaceContainer *>(parentWrapper->container());
parentWrapper->addSubSurface(wrapper);
if (oldContainer != parentContainer) {
if (oldContainer)
oldContainer->removeSurface(wrapper);
if (auto ws = qobject_cast<Workspace *>(parentContainer))
ws->addSurface(wrapper, parentWrapper->workspaceId());
else
parentContainer->addSurface(wrapper);
}
} else {
if (oldContainer) {
if (qobject_cast<Workspace *>(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<Workspace *>(parentContainer))
ws->addSurface(wrapper, parentWrapper->workspaceId());
else
parentContainer->addSurface(wrapper);
}
// else do nothing, already in workspace
} else {
return;
}
}

if (oldContainer) {
if (qobject_cast<Workspace *>(oldContainer) == nullptr) {
oldContainer->removeSurface(wrapper);
m_workspace->addSurface(wrapper);
}
// else do nothing, already in workspace
} else {
m_workspace->addSurface(wrapper);
}
}

Expand Down Expand Up @@ -578,6 +600,21 @@

void ShellHandler::onXWaylandSurfaceAdded(WXWaylandSurface *surface)
{
connect(surface, &QObject::destroyed, this, [this, surface] {
m_pendingXWaylandOverrideRedirectFocuses.remove(surface);
});

surface->safeConnect(&WXWaylandSurface::focusIn,
this,
[this, surface = QPointer<WXWaylandSurface>(surface)] {
handleXWaylandOverrideRedirectFocus(surface.data());
});
surface->safeConnect(&WXWaylandSurface::grabFocus,
this,
[this, surface = QPointer<WXWaylandSurface>(surface)] {
handleXWaylandOverrideRedirectFocus(surface.data());
});

surface->safeConnect(&WXWaylandSurface::associated,
this,
[this, surface = QPointer<WXWaylandSurface>(surface)] {
Expand Down Expand Up @@ -625,6 +662,7 @@
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();
Expand All @@ -641,6 +679,27 @@
}
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();
Expand All @@ -657,6 +716,101 @@
});
}

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();
Expand Down Expand Up @@ -749,11 +903,34 @@
}

// Initialize wrapper
auto updateSurfaceWithParentContainer = [this, wrapper, surface] {
updateWrapperContainer(wrapper, surface->parentSurface());
auto updateSurfaceWithParentContainer =
[self = QPointer<ShellHandler>(this),
wrapper = QPointer<SurfaceWrapper>(wrapper),
surface = QPointer<WXWaylandSurface>(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());
Expand All @@ -764,6 +941,43 @@
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<WXWaylandSurface>(surface),
wrapper = QPointer<SurfaceWrapper>(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);
}

Expand Down
8 changes: 7 additions & 1 deletion src/core/shellhandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@

#include <wglobal.h>

#include <qwglobal.h>

Check warning on line 12 in src/core/shellhandler.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <qwglobal.h> not found. Please note: Cppcheck does not need standard library headers to get proper results.

#include <QHash>
#include <QByteArray>

Check warning on line 14 in src/core/shellhandler.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QByteArray> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QList>

Check warning on line 15 in src/core/shellhandler.h

View workflow job for this annotation

GitHub Actions / cppcheck

Include file: <QList> not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <QMap>
#include <QObject>
#include <QPointer>
Expand Down Expand Up @@ -168,6 +168,11 @@
void onInitialPropertiesReady(WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *surface,
const QString &appId,
const QMap<xcb_atom_t, QByteArray> &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;
Expand Down Expand Up @@ -201,6 +206,7 @@
// Pending toplevel surfaces (XDG or XWayland) awaiting async AppId resolve; callbacks continue
// only if the pointer remains in this list
QList<WAYLIB_SERVER_NAMESPACE::WToplevelSurface *> m_pendingAppIdResolveToplevels;
QSet<WAYLIB_SERVER_NAMESPACE::WXWaylandSurface *> m_pendingXWaylandOverrideRedirectFocuses;
// New protocol based app id resolver (optional, may be null if module not loaded)
AppIdResolverManager *m_appIdResolverManager = nullptr;
WindowConfigStore *m_windowConfigStore = nullptr;
Expand Down
Loading
Loading