Wayland client: use wl_keyboard to determine active state

Commit d4d47f2a043f3e7548ba10c69fb54637511ba563 made QWaylandDisplay
use the xdgshell's active state for QWindow::isActive(), instead of
using wl_keyboard activate/deactivate events.

That seems to have been a misunderstanding, since xdgshell activation
is only supposed to be used to determine visual appearance, and there
is an explicit warning not to assume it means focus.

This commit reverts this logic back to listening to wl_keyboard.
It adds a fallback when there is no wl_keyboard available to handle
activated/deactivated events through xdg-shell, in order to fix
QTBUG-53702.

windowStates is handled so that we're not using the Xdg hint for
anything with QWindowSystemInterface::handleWindowStateChanged or
anything where we need to track only having one active.

We are still exposing it for decorations, which is the only reason to
use the Xdghint over keyboard focus - so you can keep the toplevel
active whilst you show a popup.

Change-Id: I4343d2ed9fb5b066cde95628ed0b4ccc84a424db
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@qt.io>
This commit is contained in:
Méven Car 2021-08-18 18:28:20 +02:00 committed by Paul Olav Tvete
parent 0a80b46583
commit 55d2590abf
8 changed files with 30 additions and 45 deletions

View File

@ -67,11 +67,6 @@ QWaylandXdgSurface::Toplevel::Toplevel(QWaylandXdgSurface *xdgSurface)
QWaylandXdgSurface::Toplevel::~Toplevel() QWaylandXdgSurface::Toplevel::~Toplevel()
{ {
if (m_applied.states & Qt::WindowActive) {
QWaylandWindow *window = m_xdgSurface->window();
window->display()->handleWindowDeactivated(window);
}
// The protocol spec requires that the decoration object is deleted before xdg_toplevel. // The protocol spec requires that the decoration object is deleted before xdg_toplevel.
delete m_decoration; delete m_decoration;
m_decoration = nullptr; m_decoration = nullptr;
@ -85,17 +80,16 @@ void QWaylandXdgSurface::Toplevel::applyConfigure()
if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen))) if (!(m_applied.states & (Qt::WindowMaximized|Qt::WindowFullScreen)))
m_normalSize = m_xdgSurface->m_window->windowFrameGeometry().size(); m_normalSize = m_xdgSurface->m_window->windowFrameGeometry().size();
if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)) if ((m_pending.states & Qt::WindowActive) && !(m_applied.states & Qt::WindowActive)
&& !m_xdgSurface->m_window->display()->isKeyboardAvailable())
m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window); m_xdgSurface->m_window->display()->handleWindowActivated(m_xdgSurface->m_window);
if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)) if (!(m_pending.states & Qt::WindowActive) && (m_applied.states & Qt::WindowActive)
&& !m_xdgSurface->m_window->display()->isKeyboardAvailable())
m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window); m_xdgSurface->m_window->display()->handleWindowDeactivated(m_xdgSurface->m_window);
// TODO: none of the other plugins send WindowActive either, but is it on purpose?
Qt::WindowStates statesWithoutActive = m_pending.states & ~Qt::WindowActive;
m_xdgSurface->m_window->handleToplevelWindowTilingStatesChanged(m_toplevelStates); m_xdgSurface->m_window->handleToplevelWindowTilingStatesChanged(m_toplevelStates);
m_xdgSurface->m_window->handleWindowStatesChanged(statesWithoutActive); m_xdgSurface->m_window->handleWindowStatesChanged(m_pending.states);
if (m_pending.size.isEmpty()) { if (m_pending.size.isEmpty()) {
// An empty size in the configure means it's up to the client to choose the size // An empty size in the configure means it's up to the client to choose the size

View File

@ -69,20 +69,6 @@ QWaylandShellSurface *QWaylandXdgShellIntegration::createShellSurface(QWaylandWi
return m_xdgShell->getXdgSurface(window); return m_xdgShell->getXdgSurface(window);
} }
void QWaylandXdgShellIntegration::handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus)
{
if (newFocus) {
auto *xdgSurface = qobject_cast<QWaylandXdgSurface *>(newFocus->shellSurface());
if (xdgSurface && !xdgSurface->handlesActiveState())
m_display->handleWindowActivated(newFocus);
}
if (oldFocus && qobject_cast<QWaylandXdgSurface *>(oldFocus->shellSurface())) {
auto *xdgSurface = qobject_cast<QWaylandXdgSurface *>(oldFocus->shellSurface());
if (xdgSurface && !xdgSurface->handlesActiveState())
m_display->handleWindowDeactivated(oldFocus);
}
}
void *QWaylandXdgShellIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window) void *QWaylandXdgShellIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window)
{ {
if (auto waylandWindow = static_cast<QWaylandWindow *>(window->handle())) { if (auto waylandWindow = static_cast<QWaylandWindow *>(window->handle())) {

View File

@ -65,7 +65,6 @@ public:
QWaylandXdgShellIntegration() {} QWaylandXdgShellIntegration() {}
bool initialize(QWaylandDisplay *display) override; bool initialize(QWaylandDisplay *display) override;
QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override; QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) override;
void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override; void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) override;
private: private:

View File

@ -578,14 +578,10 @@ void QWaylandDisplay::handleKeyboardFocusChanged(QWaylandInputDevice *inputDevic
if (mLastKeyboardFocus == keyboardFocus) if (mLastKeyboardFocus == keyboardFocus)
return; return;
if (mWaylandIntegration->mShellIntegration) { if (keyboardFocus)
mWaylandIntegration->mShellIntegration->handleKeyboardFocusChanged(keyboardFocus, mLastKeyboardFocus); handleWindowActivated(keyboardFocus);
} else { if (mLastKeyboardFocus)
if (keyboardFocus) handleWindowDeactivated(mLastKeyboardFocus);
handleWindowActivated(keyboardFocus);
if (mLastKeyboardFocus)
handleWindowDeactivated(mLastKeyboardFocus);
}
mLastKeyboardFocus = keyboardFocus; mLastKeyboardFocus = keyboardFocus;
} }
@ -630,6 +626,13 @@ QWaylandInputDevice *QWaylandDisplay::defaultInputDevice() const
return mInputDevices.isEmpty() ? 0 : mInputDevices.first(); return mInputDevices.isEmpty() ? 0 : mInputDevices.first();
} }
bool QWaylandDisplay::isKeyboardAvailable() const
{
return std::any_of(
mInputDevices.constBegin(), mInputDevices.constEnd(),
[this](const QWaylandInputDevice *device) { return device->keyboard() != nullptr; });
}
#if QT_CONFIG(cursor) #if QT_CONFIG(cursor)
QWaylandCursor *QWaylandDisplay::waylandCursor() QWaylandCursor *QWaylandDisplay::waylandCursor()

View File

@ -220,6 +220,7 @@ public:
void destroyFrameQueue(const FrameQueue &q); void destroyFrameQueue(const FrameQueue &q);
void dispatchQueueWhile(wl_event_queue *queue, std::function<bool()> condition, int timeout = -1); void dispatchQueueWhile(wl_event_queue *queue, std::function<bool()> condition, int timeout = -1);
bool isKeyboardAvailable() const;
public slots: public slots:
void blockingReadEvents(); void blockingReadEvents();
void flushRequests(); void flushRequests();

View File

@ -96,7 +96,6 @@ QWaylandWindow::QWaylandWindow(QWindow *window, QWaylandDisplay *display)
QWaylandWindow::~QWaylandWindow() QWaylandWindow::~QWaylandWindow()
{ {
mDisplay->destroyFrameQueue(mFrameQueue); mDisplay->destroyFrameQueue(mFrameQueue);
mDisplay->handleWindowDestroyed(this);
delete mWindowDecoration; delete mWindowDecoration;
@ -265,6 +264,8 @@ void QWaylandWindow::reset()
mMask = QRegion(); mMask = QRegion();
mQueuedBuffer = nullptr; mQueuedBuffer = nullptr;
mDisplay->handleWindowDestroyed(this);
} }
QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface) QWaylandWindow *QWaylandWindow::fromWlSurface(::wl_surface *surface)
@ -1273,7 +1274,10 @@ Qt::WindowStates QWaylandWindow::windowStates() const
void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states) void QWaylandWindow::handleWindowStatesChanged(Qt::WindowStates states)
{ {
createDecoration(); createDecoration();
QWindowSystemInterface::handleWindowStateChanged(window(), states, mLastReportedWindowStates); Qt::WindowStates statesWithoutActive = states & ~Qt::WindowActive;
Qt::WindowStates lastStatesWithoutActive = mLastReportedWindowStates & ~Qt::WindowActive;
QWindowSystemInterface::handleWindowStateChanged(window(), statesWithoutActive,
lastStatesWithoutActive);
mLastReportedWindowStates = states; mLastReportedWindowStates = states;
} }

View File

@ -73,12 +73,6 @@ public:
return true; return true;
} }
virtual QWaylandShellSurface *createShellSurface(QWaylandWindow *window) = 0; virtual QWaylandShellSurface *createShellSurface(QWaylandWindow *window) = 0;
virtual void handleKeyboardFocusChanged(QWaylandWindow *newFocus, QWaylandWindow *oldFocus) {
if (newFocus)
m_display->handleWindowActivated(newFocus);
if (oldFocus)
m_display->handleWindowDeactivated(oldFocus);
}
virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) { virtual void *nativeResourceForWindow(const QByteArray &resource, QWindow *window) {
Q_UNUSED(resource); Q_UNUSED(resource);
Q_UNUSED(window); Q_UNUSED(window);

View File

@ -31,6 +31,7 @@
#include <QtGui/QRasterWindow> #include <QtGui/QRasterWindow>
#include <QtGui/qpa/qplatformnativeinterface.h> #include <QtGui/qpa/qplatformnativeinterface.h>
#include <QtWaylandClient/private/wayland-wayland-client-protocol.h> #include <QtWaylandClient/private/wayland-wayland-client-protocol.h>
#include <QtWaylandClient/private/qwaylandwindow_p.h>
using namespace MockCompositor; using namespace MockCompositor;
@ -155,9 +156,12 @@ void tst_xdgshell::configureStates()
// Toplevel windows don't know their position on xdg-shell // Toplevel windows don't know their position on xdg-shell
// QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled // QCOMPARE(window.frameGeometry().topLeft(), QPoint()); // TODO: this doesn't currently work when window decorations are enabled
// QEXPECT_FAIL("", "configure has already been acked, we shouldn't have to wait for isActive", Continue); // window.windowstate() is driven by keyboard focus, however for decorations we want to follow
// QVERIFY(window.isActive()); // XDGShell this is internal to QtWayland so it is queried directly
QTRY_VERIFY(window.isActive()); // Just make sure it eventually get's set correctly auto waylandWindow = static_cast<QtWaylandClient::QWaylandWindow *>(window.handle());
Q_ASSERT(waylandWindow);
QTRY_VERIFY(waylandWindow->windowStates().testFlag(
Qt::WindowActive)); // Just make sure it eventually get's set correctly
const QSize screenSize(640, 480); const QSize screenSize(640, 480);
const uint maximizedSerial = exec([=] { const uint maximizedSerial = exec([=] {