fix(niri): filter out duplicates in workspace list#2528
fix(niri): filter out duplicates in workspace list#2528gpayer wants to merge 1 commit intonoctalia-dev:mainfrom
Conversation
|
Testing this and it seems to have fixed the issue for me, thanks! A note, not sure if this is how it originally behaved, but empty workspaces on monitors disappear when you unfocus the monitor. |
That was always the case. You only see the "next" empty workspace. But unnamed empty workspaces are closed when you unfocus them. Only named empty workspaces remain, I just tested this. So works as before I'd say. |
|
FWIW the root cause sits one layer down in noctalia-qs, not in the QML.
// model.hpp
for (auto* object: newValues) {
if (this->mValuesList.length() == oi || this->mValuesList.at(oi) != object) {
this->insertObject(object, oi); // old position never removed
}
oi++;
}Anything that makes the post-update sort a non-trivial permutation trips it. For me that's docking the laptop (all workspaces migrate eDP-1 → DP-12, so the sort key flips), for #2463 it's moving a window between workspaces. Traced with No backport patch for noctalia-qs 0.0.10--- a/src/wayland/niri/ipc/connection.cpp
+++ b/src/wayland/niri/ipc/connection.cpp
@@ -25,8 +25,34 @@
namespace qs::niri::ipc {
namespace {
+
QS_LOGGING_CATEGORY(logNiriIpc, "quickshell.niri.ipc", QtWarningMsg);
QS_LOGGING_CATEGORY(logNiriIpcEvents, "quickshell.niri.ipc.events", QtWarningMsg);
+
+// diffUpdate inserts without removing the prior row; reorder after sort avoids duplicate model rows.
+void reorderNiriWorkspaceModel(ObjectModel<NiriWorkspace>& model, const QList<NiriWorkspace*>& sorted) {
+ QList<NiriWorkspace*> order;
+ order.reserve(sorted.size());
+ for (auto* w: sorted) {
+ if (!order.contains(w)) order.append(w);
+ }
+
+ auto& list = model.valueList();
+ for (qsizetype i = 0; i < list.length();) {
+ auto* o = list.at(i);
+ if (!order.contains(o) || list.indexOf(o) != i) model.removeAt(i);
+ else i++;
+ }
+
+ for (qsizetype t = 0; t < order.length(); ++t) {
+ auto* w = order.at(t);
+ auto c = list.indexOf(w);
+ if (c < 0 || c == t) continue;
+ model.removeAt(c);
+ model.insertObject(w, t);
+ }
+}
+
} // namespace
void NiriIpcEvent::set(const QString& type, const QJsonObject& data) {
@@ -46,12 +72,6 @@ NiriIpc::NiriIpc() {
return;
}
- this->workspaceRefreshTimer.setInterval(50);
- this->workspaceRefreshTimer.setSingleShot(true);
- QObject::connect(&this->workspaceRefreshTimer, &QTimer::timeout, this, [this]() {
- this->refreshWorkspaces();
- });
-
// clang-format off
QObject::connect(&this->eventSocket, &QLocalSocket::errorOccurred, this, &NiriIpc::eventSocketError);
QObject::connect(&this->eventSocket, &QLocalSocket::stateChanged, this, &NiriIpc::eventSocketStateChanged);
@@ -170,8 +190,8 @@ void NiriIpc::handleWorkspaceActivated(const QJsonObject& data) {
auto* activated = this->findWorkspaceById(workspaceId);
if (!activated) {
- // Unknown workspace, do a full refresh
- this->workspaceRefreshTimer.start();
+ // Unknown workspace — same idea as Hyprland falling back to j/workspaces
+ this->refreshWorkspaces();
return;
}
@@ -195,8 +215,13 @@ void NiriIpc::handleWorkspaceActivated(const QJsonObject& data) {
Qt::endPropertyUpdateGroup();
emit this->workspacesUpdated();
- // Also refresh to catch any other state changes
- this->workspaceRefreshTimer.start();
+
+ // Event-stream ordering can leave is_focused stale on toplevels (e.g. empty workspace:
+ // no WindowFocusChanged before the shell reads state). Request::Windows is authoritative;
+ // see https://docs.rs/niri-ipc/latest/niri_ipc/
+ if (isFocused) {
+ this->refreshWindows();
+ }
}
void NiriIpc::handleWorkspaceActiveWindowChanged(const QJsonObject& data) {
@@ -252,7 +277,6 @@ void NiriIpc::handleWindowOpenedOrChanged(const QJsonObject& data) {
}
emit this->windowsUpdated();
- this->workspaceRefreshTimer.start();
}
void NiriIpc::handleWindowClosed(const QJsonObject& data) {
@@ -275,7 +299,6 @@ void NiriIpc::handleWindowClosed(const QJsonObject& data) {
qCDebug(logNiriIpc) << "Window closed with id" << windowId;
emit this->windowsUpdated();
- this->workspaceRefreshTimer.start();
}
void NiriIpc::handleWindowsChanged(const QJsonObject& data) {
@@ -598,7 +621,7 @@ void NiriIpc::updateWorkspacesFromArray(const QJsonArray& workspacesArray) {
}
return a->bindableIdx().value() < b->bindableIdx().value();
});
- this->mWorkspaces.diffUpdate(sortedList);
+ reorderNiriWorkspaceModel(this->mWorkspaces, sortedList);
emit this->workspacesUpdated();
}
--- a/src/wayland/niri/ipc/connection.hpp
+++ b/src/wayland/niri/ipc/connection.hpp
@@ -8,7 +8,6 @@
#include <qobject.h>
#include <qproperty.h>
#include <qqmlintegration.h>
-#include <qtimer.h>
#include <qtmetamacros.h>
#include <qtypes.h>
@@ -145,8 +144,6 @@ private:
bool requestingWorkspaces = false;
bool requestingWindows = false;
- QTimer workspaceRefreshTimer;
-
ObjectModel<NiriOutput> mOutputs {this};
ObjectModel<NiriWorkspace> mWorkspaces {this};
ObjectModel<NiriWindow> mWindows {this}; |
|
Interesting. I am using the official noctalia flake to get the package: I'll dig into this tomorrow, maybe removing the |
|
Here is my attempt: noctalia-dev/noctalia-qs#35 |
Pull Request
This fixes the workspace multiplication bug when running noctalia in niri.
Motivation
The bug was highly annoying and looking at the code seemed easy to fix.
Type of Change
Mark the relevant option with an "x".
Related Issue
Testing
Describe how you tested your changes and mark the relevant items.
Screenshots / Videos
For screenshots showing the error, see #2463. This does not happen anymore after the fix.
Checklist