Strange behavior while debugging Yakuake bug 485069 (window size wrong when closed on a smaller screen)

I tried debugging Yakuake bug 485069, and I might have found a way around it. However, I do not really understand what is actually going on without the workaround. While debugging, I noticed some strange behavior which makes me wonder if the issue is really within Yakuake or somewhere else. I therefore tagged this topic (also) with kwin to summon some more expertise. Maybe KWin is even part of the problem, or maybe I am just misunderstanding something.

For understanding what I am talking about, please have a look at bug 485069 first. Note: The issue is IMO best observed when disabling animations for “Window open/close” and “Sliding popups”. Also note that we are dealing with Wayland here.

When Yakuake is opened via the hotkey, the following function is executed:

void MainWindow::toggleWindowState()
{
    if (m_isWayland) {
        auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
                                                      QStringLiteral("/StrutManager"),
                                                      QStringLiteral("org.kde.PlasmaShell.StrutManager"),
                                                      QStringLiteral("availableScreenRect"));
        message.setArguments({QGuiApplication::screens().at(getScreen())->name()});
        QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);

        QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=, this]() {
            QDBusPendingReply<QRect> reply = *watcher;
            m_availableScreenRect = reply.isValid() ? reply.value() : QRect();
            setWindowGeometry(Settings::width(), Settings::height(), Settings::position());
            watcher->deleteLater();
        });

        _toggleWindowState();
    } else {
        _toggleWindowState();
    }
}

Note that _toggleWindowState() is called before m_availableScreenRect is potentially updated. This was introduced in commit d308887. This actually makes sense to me, however, this seems to trigger the problem. When querying availableScreenRect synchronously, the issue disappears:

void MainWindow::toggleWindowState()
{
    if (m_isWayland) {
        auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
                                                      QStringLiteral("/StrutManager"),
                                                      QStringLiteral("org.kde.PlasmaShell.StrutManager"),
                                                      QStringLiteral("availableScreenRect"));
        message.setArguments({QGuiApplication::screens().at(getScreen())->name()});

        QDBusPendingReply<QRect> reply = QDBusConnection::sessionBus().call(message);
        m_availableScreenRect = reply.isValid() ? reply.value() : QRect();
        setWindowGeometry(Settings::width(), Settings::height(), Settings::position());
        // ^ TODO: Actually applyWindowGeometry() should be used here to make
        //         this work with the fullsceen state, however, that’s a
        //         different issue.

        _toggleWindowState();
    } else {
        _toggleWindowState();
    }
}

Although this fixes the issue, I wonder why it does not work without the fix.

To add to the confusion, for debugging purposes, I also tried the following, where I added a timer to delay updating the window geometry even more after the DBus call returned:

void MainWindow::toggleWindowState()
{
    if (m_isWayland) {
        auto message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.plasmashell"),
                                                      QStringLiteral("/StrutManager"),
                                                      QStringLiteral("org.kde.PlasmaShell.StrutManager"),
                                                      QStringLiteral("availableScreenRect"));
        message.setArguments({QGuiApplication::screens().at(getScreen())->name()});

        QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
        QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);

        QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [=, this]() {
            QDBusPendingReply<QRect> reply = *watcher;
            m_availableScreenRect = reply.isValid() ? reply.value() : QRect();
            QTimer::singleShot(30, this, [this]() {
                applyWindowGeometry();
            });
            watcher->deleteLater();
        });

        _toggleWindowState();
    } else {
        _toggleWindowState();
    }
}

Now it gets really weird, because the behavior depends on the timer timeout:

  • With a small timeout (e.g., 1ms), I observe the original issue. (Not much of a surprise, I guess.)
  • With larger timeout (e.g., 500ms), everything seems to work as expected. Although not immediately, but after 500ms everything is fine.
  • With a medium timeout (e.g., 30ms), when opening Yakuake the first time, sometimes the dimensions of the window might be wrong. Subsequent openings work like in the previous item.

(When playing around with this, wait for the timeout also when hiding Yakuake, otherwise it gets really messy.)

With some printf()/qDebug() debugging, I noticed that, when window eventually ends up at the wrong place or with wrong dimensions, there is an additional resizeEvent received by MainWindow after the size was already correct before. The resize event uses an outdated size, i.e., a size that made sense earlier, however, not anymore when the event hits. I have absolutely no clue where this is coming from. It feels as if events are received out of order. Maybe this is due to the asynchronous nature of Wayland, but this is just a hunch.

Does anyone have an idea what might going on here?

(Sorry for this wall of text. Due to my confusion, I was not able to further condense this.)