hyprland/ipc: support workspace addresses

This commit is contained in:
outfoxxed committed 2026-09-20 22:04:49 -07:00
1 parent c6a516096d
commit fae96f1a5b
7 files changed
+86 -48

No files matched your search

+4
View File
@@ -1,3 +1,7 @@
## Other Changes
- Added support for Hyprland's new workspace address format.
## Bug Fixes
- Fixed main process crashes on pam subprocess misbehavior.
+42 -37
View File
@@ -188,12 +188,12 @@ void HyprlandIpc::makeRequest(
};
auto errorCallback =
[this, requestSocket, request, callback](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
QObject::disconnect(requestSocket, nullptr, this, nullptr);
requestSocket->deleteLater();
callback(false, {});
};
[this, requestSocket, request, callback](QLocalSocket::LocalSocketError error) {
qCWarning(logHyprlandIpc) << "Error making request:" << error << "request:" << request;
QObject::disconnect(requestSocket, nullptr, this, nullptr);
requestSocket->deleteLater();
callback(false, {});
};
QObject::connect(requestSocket, &QLocalSocket::connected, this, connectedCallback);
QObject::connect(requestSocket, &QLocalSocket::errorOccurred, this, errorCallback);
@@ -307,7 +307,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
const auto& mList = this->mMonitors.valueList();
auto name = QString::fromUtf8(event->data);
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -332,10 +332,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "createworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
qCDebug(logHyprlandIpc) << "Workspace created with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace created with address" << address << "name" << name;
auto* workspace = this->findWorkspaceByName(name, false);
auto existed = workspace != nullptr;
@@ -344,7 +344,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace = new HyprlandWorkspace(this);
}
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
if (!existed) {
this->refreshWorkspaces(false);
@@ -353,17 +353,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
} else if (event->name == "destroyworkspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) {
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << id << "name" << name
qCWarning(logHyprlandIpc) << "Got removal for workspace id" << address << "name" << name
<< "which was not previously tracked.";
return;
}
@@ -371,7 +371,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
auto index = workspaceIter - mList.begin();
auto* workspace = *workspaceIter;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << id << "name" << name;
qCDebug(logHyprlandIpc) << "Workspace removed with id" << address << "name" << name;
this->mWorkspaces.removeAt(index);
// workspaces have not been observed to be referenced after deletion
@@ -400,10 +400,10 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
this->setFocusedMonitor(monitor);
monitor->setActiveWorkspace(workspace);
qCDebug(logHyprlandIpc) << "Monitor" << name << "focused with workspace"
<< (workspace ? workspace->bindableId().value() : -1);
<< (workspace ? workspace->bindableAddress().value() : "<unk>");
} else if (event->name == "workspacev2") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
if (this->bFocusedMonitor != nullptr) {
@@ -414,7 +414,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
} else if (event->name == "moveworkspacev2") {
auto args = event->parseView(3);
auto id = args.at(0).toInt();
auto id = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
auto monitorName = QString::fromUtf8(args.at(2));
@@ -425,18 +425,18 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
workspace->setMonitor(monitor);
} else if (event->name == "renameworkspace") {
auto args = event->parseView(2);
auto id = args.at(0).toInt();
auto address = QString::fromUtf8(args.at(0));
auto name = QString::fromUtf8(args.at(1));
const auto& mList = this->mWorkspaces.valueList();
auto workspaceIter = std::ranges::find_if(mList, [id](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableAddress().value() == address;
});
if (workspaceIter == mList.end()) return;
qCDebug(logHyprlandIpc) << "Workspace with id" << id << "renamed from"
qCDebug(logHyprlandIpc) << "Workspace with address" << address << "renamed from"
<< (*workspaceIter)->bindableName().value() << "to" << name;
(*workspaceIter)->bindableName().setValue(name);
@@ -488,7 +488,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
if (!ok) return;
const auto& mList = this->mToplevels.valueList();
auto toplevelIter = std::ranges::find_if(mList, [windowAddress](HyprlandToplevel* m) {
auto toplevelIter = std::ranges::find_if(mList, [&](HyprlandToplevel* m) {
return m->address() == windowAddress;
});
@@ -580,14 +580,17 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
}
}
HyprlandWorkspace*
HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id) {
HyprlandWorkspace* HyprlandIpc::findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address
) {
const auto& mList = this->mWorkspaces.valueList();
HyprlandWorkspace* workspace = nullptr;
if (id != -1) {
if (!address.isEmpty()) {
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
workspace = workspaceIter == mList.end() ? nullptr : *workspaceIter;
@@ -605,10 +608,11 @@ HyprlandIpc::findWorkspaceByName(const QString& name, bool createIfMissing, qint
return workspace;
} else if (createIfMissing) {
qCDebug(logHyprlandIpc) << "Workspace" << name
<< "requested before creation, performing early init with id" << id;
<< "requested before creation, performing early init with address"
<< address;
auto* workspace = new HyprlandWorkspace(this);
workspace->updateInitial(id, name);
workspace->updateInitial(address, name);
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
return workspace;
} else {
@@ -628,15 +632,16 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto json = QJsonDocument::fromJson(resp).array();
const auto& mList = this->mWorkspaces.valueList();
auto ids = QVector<quint32>();
auto addresses = QVector<QString>();
for (auto entry: json) {
auto object = entry.toObject().toVariantMap();
auto id = object.value("id").toInt();
auto address = object.value("address").toString();
if (address.isEmpty()) address = QString::number(object.value("id").toInt());
auto workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == id;
return m->bindableAddress().value() == address;
});
// Only fall back to name-based filtering as a last resort, for workspaces where
@@ -645,7 +650,7 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
auto name = object.value("name").toString();
workspaceIter = std::ranges::find_if(mList, [&](HyprlandWorkspace* m) {
return m->bindableId().value() == -1 && m->bindableName().value() == name;
return m->bindableAddress().value().isEmpty() && m->bindableName().value() == name;
});
}
@@ -663,14 +668,14 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
this->mWorkspaces.insertObjectSorted(workspace, &HyprlandIpc::compareWorkspaces);
}
ids.push_back(id);
addresses.push_back(address);
}
if (canCreate) {
auto removedWorkspaces = QVector<HyprlandWorkspace*>();
for (auto* workspace: mList) {
if (!ids.contains(workspace->bindableId().value())) {
if (!addresses.contains(workspace->bindableAddress().value())) {
removedWorkspaces.push_back(workspace);
}
}
@@ -752,7 +757,7 @@ HyprlandMonitor*
HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
const auto& mList = this->mMonitors.valueList();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
@@ -820,7 +825,7 @@ void HyprlandIpc::refreshMonitors(bool canCreate) {
auto object = entry.toObject().toVariantMap();
auto name = object.value("name").toString();
auto monitorIter = std::ranges::find_if(mList, [name](HyprlandMonitor* m) {
auto monitorIter = std::ranges::find_if(mList, [&](HyprlandMonitor* m) {
return m->bindableName().value() == name;
});
+5 -1
View File
@@ -102,7 +102,11 @@ public:
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels();
// No byId because these preemptively create objects. The given id is set if created.
HyprlandWorkspace* findWorkspaceByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandWorkspace* findWorkspaceByName(
const QString& name,
bool createIfMissing,
const QString& address = QString()
);
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
HyprlandToplevel* findToplevelByAddress(quint64 address, bool createIfMissing);
+4 -1
View File
@@ -28,7 +28,9 @@ void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QStrin
void HyprlandMonitor::updateFromObject(QVariantMap object) {
auto activeWorkspaceObj = object.value("activeWorkspace").value<QVariantMap>();
auto activeWorkspaceAddress = activeWorkspaceObj.value("address").value<QString>();
auto activeWorkspaceId = activeWorkspaceObj.value("id").value<qint32>();
if (activeWorkspaceAddress.isEmpty()) activeWorkspaceAddress = QString::number(activeWorkspaceId);
auto activeWorkspaceName = activeWorkspaceObj.value("name").value<QString>();
auto focused = object.value("focused").value<bool>();
@@ -46,7 +48,8 @@ void HyprlandMonitor::updateFromObject(QVariantMap object) {
if (this->bActiveWorkspace == nullptr
|| this->bActiveWorkspace->bindableName().value() != activeWorkspaceName)
{
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId);
auto* workspace =
this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceAddress);
workspace->setMonitor(this);
this->setActiveWorkspace(workspace);
}
+17 -7
View File
@@ -18,11 +18,18 @@ QVariantMap HyprlandWorkspace::lastIpcObject() const { return this->mLastIpcObje
HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
this->bActive.setBinding([this]() {
this->bId.setBinding([this] {
auto idOk = false;
auto id = this->bAddress.value().toInt(&idOk);
if (!idOk) id = -1;
return id;
});
this->bActive.setBinding([this] {
return this->bMonitor.value() && this->bMonitor->bindableActiveWorkspace().value() == this;
});
this->bFocused.setBinding([this]() {
this->bFocused.setBinding([this] {
return this->ipc->bindableFocusedWorkspace().value() == this;
});
@@ -35,9 +42,9 @@ HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
Qt::endPropertyUpdateGroup();
}
void HyprlandWorkspace::updateInitial(qint32 id, const QString& name) {
void HyprlandWorkspace::updateInitial(const QString& address, const QString& name) {
Qt::beginPropertyUpdateGroup();
this->bId = id;
this->bAddress = address;
this->bName = name;
Qt::endPropertyUpdateGroup();
}
@@ -47,11 +54,14 @@ void HyprlandWorkspace::updateFromObject(QVariantMap object) {
auto monitorName = object.value("monitor").value<QString>();
auto hasFullscreen = object.value("hasfullscreen").value<bool>();
auto initial = this->bId == -1;
auto initial = this->bAddress.value().isEmpty();
// ID cannot be updated after creation
// Address cannot be updated after creation
if (initial) {
this->bId = object.value("id").value<qint32>();
auto address = object.value("address").value<QString>();
auto id = object.value("id").value<qint32>();
if (address.isEmpty()) address = QString::number(id);
this->bAddress = address;
}
// No events we currently handle give a workspace id but not a name,
+13 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include <qbytearrayview.h>
#include <qcontainerfwd.h>
#include <qjsonobject.h>
#include <qobject.h>
#include <qproperty.h>
@@ -18,6 +19,13 @@ class HyprlandMonitor;
class HyprlandWorkspace: public QObject {
Q_OBJECT;
// clang-format off
// > [!NOTE] On Hyprland versions prior to 0.57, this property is set to @@id as a string for compatibility.
// >
// > Prefer this property over @@id for new code.
Q_PROPERTY(QString address READ default NOTIFY addressChanged BINDABLE bindableAddress);
// > [!NOTE] On Hyprland versions 0.57 and later, this property is set to @@address as an integer for compatibility.
// >
// > After 0.57, this property will equal `-1` for non-numbered workspaces.
Q_PROPERTY(qint32 id READ default NOTIFY idChanged BINDABLE bindableId);
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// If this workspace is currently active on its monitor. See also @@focused.
@@ -47,7 +55,7 @@ class HyprlandWorkspace: public QObject {
public:
explicit HyprlandWorkspace(HyprlandIpc* ipc);
void updateInitial(qint32 id, const QString& name);
void updateInitial(const QString& address, const QString& name);
void updateFromObject(QVariantMap object);
/// Activate the workspace.
@@ -58,6 +66,7 @@ public:
/// > ```
Q_INVOKABLE void activate();
[[nodiscard]] QBindable<QString> bindableAddress() { return &this->bAddress; }
[[nodiscard]] QBindable<qint32> bindableId() { return &this->bId; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
@@ -75,7 +84,9 @@ public:
void removeToplevel(HyprlandToplevel* toplevel);
signals:
void addressChanged();
void idChanged();
void typeChanged();
void nameChanged();
void activeChanged();
void focusedChanged();
@@ -97,6 +108,7 @@ private:
ObjectModel<HyprlandToplevel> mToplevels {this};
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bAddress, &HyprlandWorkspace::addressChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(HyprlandWorkspace, qint32, bId, -1, &HyprlandWorkspace::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, QString, bName, &HyprlandWorkspace::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bActive, &HyprlandWorkspace::activeChanged);
@@ -16,7 +16,7 @@ FloatingWindow {
color: "lightgray"
ColumnLayout {
Text { text: `Workspace ${wsDelegate.modelData.id} on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
Text { text: `Workspace (addr ${wsDelegate.modelData.address}; id ${wsDelegate.modelData.id}; name: ${wsDelegate.modelData.name}) on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
ColumnLayout {
Repeater {