i3/ipc: general cleanup + add active property

Brings the I3 ipc interface inline with the Hyprland one.
This commit is contained in:
outfoxxed 2025-03-27 00:05:05 -07:00
parent 8f11d60999
commit 67b2682604
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
8 changed files with 153 additions and 135 deletions

View file

@ -73,6 +73,11 @@ I3Ipc::I3Ipc() {
}
}
this->bFocusedWorkspace.setBinding([this]() -> I3Workspace* {
if (!this->bFocusedMonitor) return nullptr;
return this->bFocusedMonitor->bindableActiveWorkspace().value();
});
this->mSocketPath = sock;
// clang-format off
@ -183,59 +188,23 @@ void I3Ipc::eventSocketStateChanged(QLocalSocket::LocalSocketState state) {
}
QString I3Ipc::socketPath() const { return this->mSocketPath; }
I3Workspace* I3Ipc::focusedWorkspace() const { return this->mFocusedWorkspace; }
I3Monitor* I3Ipc::focusedMonitor() const { return this->mFocusedMonitor; }
void I3Ipc::setFocusedWorkspace(I3Workspace* workspace) {
if (workspace == this->mFocusedWorkspace) return;
if (this->mFocusedWorkspace != nullptr) {
this->mFocusedWorkspace->bindableFocused().setValue(false);
QObject::disconnect(this->mFocusedWorkspace, nullptr, this, nullptr);
}
this->mFocusedWorkspace = workspace;
if (workspace != nullptr) {
if (auto* monitor = this->mFocusedWorkspace->monitor()) {
monitor->setFocusedWorkspace(this->mFocusedWorkspace);
}
QObject::connect(workspace, &QObject::destroyed, this, &I3Ipc::onFocusedWorkspaceDestroyed);
workspace->bindableFocused().setValue(true);
this->setFocusedMonitor(workspace->monitor());
}
emit this->focusedWorkspaceChanged();
}
void I3Ipc::setFocusedMonitor(I3Monitor* monitor) {
if (monitor == this->mFocusedMonitor) return;
auto* oldMonitor = this->bFocusedMonitor.value();
if (monitor == oldMonitor) return;
if (this->mFocusedMonitor != nullptr) {
this->mFocusedMonitor->bindableFocused().setValue(false);
QObject::disconnect(this->mFocusedMonitor, nullptr, this, nullptr);
if (oldMonitor != nullptr) {
QObject::disconnect(oldMonitor, nullptr, this, nullptr);
}
this->mFocusedMonitor = monitor;
if (monitor != nullptr) {
monitor->bindableFocused().setValue(true);
QObject::connect(monitor, &QObject::destroyed, this, &I3Ipc::onFocusedMonitorDestroyed);
}
emit this->focusedMonitorChanged();
this->bFocusedMonitor = monitor;
}
void I3Ipc::onFocusedWorkspaceDestroyed() {
this->mFocusedWorkspace = nullptr;
emit this->focusedWorkspaceChanged();
}
void I3Ipc::onFocusedMonitorDestroyed() {
this->mFocusedMonitor = nullptr;
emit this->focusedMonitorChanged();
}
void I3Ipc::onFocusedMonitorDestroyed() { this->bFocusedMonitor = nullptr; }
I3Ipc* I3Ipc::instance() {
static I3Ipc* instance = nullptr; // NOLINT
@ -277,10 +246,6 @@ void I3Ipc::handleGetWorkspacesEvent(I3IpcEvent* event) {
workspace->updateFromObject(object);
if (workspace->bindableFocused().value()) {
this->setFocusedWorkspace(workspace);
}
if (!existed) {
this->mWorkspaces.insertObject(workspace);
}
@ -436,7 +401,12 @@ void I3Ipc::handleWorkspaceEvent(I3IpcEvent* event) {
}
newWorkspace->updateFromObject(newData.toObject().toVariantMap());
this->setFocusedWorkspace(newWorkspace);
if (newWorkspace->bindableMonitor().value()) {
auto* monitor = newWorkspace->bindableMonitor().value();
monitor->setFocusedWorkspace(newWorkspace);
this->bFocusedMonitor = monitor;
}
} else if (change == "empty") {
auto name = event->mData["current"]["name"].toString();
@ -445,8 +415,8 @@ void I3Ipc::handleWorkspaceEvent(I3IpcEvent* event) {
if (oldWorkspace != nullptr) {
qCInfo(logI3Ipc) << "Deleting" << oldWorkspace->bindableId().value() << name;
if (this->mFocusedWorkspace == oldWorkspace) {
this->setFocusedWorkspace(nullptr);
if (this->bFocusedWorkspace == oldWorkspace) {
this->bFocusedMonitor->setFocusedWorkspace(nullptr);
}
this->workspaces()->removeObject(oldWorkspace);

View file

@ -5,6 +5,7 @@
#include <qjsonobject.h>
#include <qlocalsocket.h>
#include <qobject.h>
#include <qproperty.h>
#include <qqml.h>
#include <qqmlintegration.h>
#include <qtmetamacros.h>
@ -91,7 +92,6 @@ public:
I3Monitor* findMonitorByName(const QString& name);
I3Workspace* findWorkspaceByID(qint32 id);
void setFocusedWorkspace(I3Workspace* workspace);
void setFocusedMonitor(I3Monitor* monitor);
void refreshWorkspaces();
@ -99,8 +99,14 @@ public:
I3Monitor* monitorFor(QuickshellScreenInfo* screen);
[[nodiscard]] I3Monitor* focusedMonitor() const;
[[nodiscard]] I3Workspace* focusedWorkspace() const;
[[nodiscard]] QBindable<I3Monitor*> bindableFocusedMonitor() const {
return &this->bFocusedMonitor;
};
[[nodiscard]] QBindable<I3Workspace*> bindableFocusedWorkspace() const {
return &this->bFocusedWorkspace;
};
[[nodiscard]] ObjectModel<I3Monitor>* monitors();
[[nodiscard]] ObjectModel<I3Workspace>* workspaces();
signals:
@ -115,7 +121,6 @@ private slots:
void eventSocketReady();
void subscribe();
void onFocusedWorkspaceDestroyed();
void onFocusedMonitorDestroyed();
private:
@ -144,8 +149,14 @@ private:
I3IpcEvent event {this};
I3Workspace* mFocusedWorkspace = nullptr;
I3Monitor* mFocusedMonitor = nullptr;
Q_OBJECT_BINDABLE_PROPERTY(I3Ipc, I3Monitor*, bFocusedMonitor, &I3Ipc::focusedMonitorChanged);
Q_OBJECT_BINDABLE_PROPERTY(
I3Ipc,
I3Workspace*,
bFocusedWorkspace,
&I3Ipc::focusedWorkspaceChanged
);
};
} // namespace qs::i3::ipc

View file

@ -1,23 +1,36 @@
#include "monitor.hpp"
#include <qcontainerfwd.h>
#include <qobject.h>
#include <qproperty.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "connection.hpp"
#include "workspace.hpp"
namespace qs::i3::ipc {
I3Workspace* I3Monitor::focusedWorkspace() const { return this->mFocusedWorkspace; };
I3Monitor::I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) {
// clang-format off
this->bFocused.setBinding([this]() { return this->ipc->bindableFocusedMonitor().value() == this; });
// clang-format on
}
QVariantMap I3Monitor::lastIpcObject() const { return this->mLastIpcObject; };
void I3Monitor::updateFromObject(const QVariantMap& obj) {
auto activeWorkspaceId = obj.value("current_workspace").value<QString>();
if (obj != this->mLastIpcObject) {
this->mLastIpcObject = obj;
emit this->lastIpcObjectChanged();
}
auto activeWorkspaceName = obj.value("current_workspace").value<QString>();
auto rect = obj.value("rect").toMap();
Qt::beginPropertyUpdateGroup();
this->bId = obj.value("id").value<qint32>();
this->bName = obj.value("name").value<QString>();
this->bPower = obj.value("power").value<bool>();
@ -26,31 +39,21 @@ void I3Monitor::updateFromObject(const QVariantMap& obj) {
this->bWidth = rect.value("width").value<qint32>();
this->bHeight = rect.value("height").value<qint32>();
this->bScale = obj.value("scale").value<qreal>();
this->bFocused = obj.value("focused").value<bool>();
Qt::endPropertyUpdateGroup();
if (activeWorkspaceId != this->mFocusedWorkspaceName) {
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceId);
if (activeWorkspaceId.isEmpty() || workspace == nullptr) { // is null when output is disabled
this->mFocusedWorkspace = nullptr;
this->mFocusedWorkspaceName = "";
if (!this->bActiveWorkspace
|| activeWorkspaceName != this->bActiveWorkspace->bindableName().value())
{
auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName);
if (activeWorkspaceName.isEmpty() || workspace == nullptr) { // is null when output is disabled
this->bActiveWorkspace = nullptr;
} else {
this->mFocusedWorkspaceName = activeWorkspaceId;
this->mFocusedWorkspace = workspace;
this->bActiveWorkspace = workspace;
}
emit this->focusedWorkspaceChanged();
};
if (obj != this->mLastIpcObject) {
this->mLastIpcObject = obj;
emit this->lastIpcObjectChanged();
}
Qt::endPropertyUpdateGroup();
}
void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) {
this->mFocusedWorkspace = workspace;
this->mFocusedWorkspaceName = workspace->bindableName().value();
emit this->focusedWorkspaceChanged();
};
void I3Monitor::setFocusedWorkspace(I3Workspace* workspace) { this->bActiveWorkspace = workspace; };
} // namespace qs::i3::ipc

View file

@ -17,8 +17,10 @@ class I3Monitor: public QObject {
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// Wether this monitor is turned on or not
Q_PROPERTY(bool power READ default NOTIFY powerChanged BINDABLE bindablePower);
/// The current workspace
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ focusedWorkspace NOTIFY focusedWorkspaceChanged);
/// The currently active workspace on this monitor, May be null.
Q_PROPERTY(qs::i3::ipc::I3Workspace* activeWorkspace READ default NOTIFY activeWorkspaceChanged BINDABLE bindableActiveWorkspace);
/// Deprecated: See @@activeWorkspace.
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ default NOTIFY activeWorkspaceChanged BINDABLE bindableActiveWorkspace);
/// The X coordinate of this monitor inside the monitor layout
Q_PROPERTY(qint32 x READ default NOTIFY xChanged BINDABLE bindableX);
/// The Y coordinate of this monitor inside the monitor layout
@ -40,7 +42,7 @@ class I3Monitor: public QObject {
QML_UNCREATABLE("I3Monitors must be retrieved from the I3Ipc object.");
public:
explicit I3Monitor(I3Ipc* ipc): QObject(ipc), ipc(ipc) {}
explicit I3Monitor(I3Ipc* ipc);
[[nodiscard]] QBindable<qint32> bindableId() { return &this->bId; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
@ -52,7 +54,10 @@ public:
[[nodiscard]] QBindable<qreal> bindableScale() { return &this->bScale; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] I3Workspace* focusedWorkspace() const;
[[nodiscard]] QBindable<I3Workspace*> bindableActiveWorkspace() {
return &this->bActiveWorkspace;
}
[[nodiscard]] QVariantMap lastIpcObject() const;
void updateFromObject(const QVariantMap& obj);
@ -63,7 +68,7 @@ signals:
void idChanged();
void nameChanged();
void powerChanged();
void focusedWorkspaceChanged();
void activeWorkspaceChanged();
void xChanged();
void yChanged();
void widthChanged();
@ -76,9 +81,8 @@ private:
I3Ipc* ipc;
QVariantMap mLastIpcObject;
I3Workspace* mFocusedWorkspace = nullptr;
QString mFocusedWorkspaceName; // use for faster change detection
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(I3Monitor, qint32, bId, -1, &I3Monitor::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Monitor, QString, bName, &I3Monitor::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Monitor, bool, bPower, &I3Monitor::powerChanged);
@ -88,6 +92,8 @@ private:
Q_OBJECT_BINDABLE_PROPERTY(I3Monitor, qint32, bHeight, &I3Monitor::heightChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(I3Monitor, qreal, bScale, 1, &I3Monitor::scaleChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Monitor, bool, bFocused, &I3Monitor::focusedChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Monitor, I3Workspace*, bActiveWorkspace, &I3Monitor::activeWorkspaceChanged);
// clang-format on
};
} // namespace qs::i3::ipc

View file

@ -1,6 +1,7 @@
#include "qml.hpp"
#include <qobject.h>
#include <qproperty.h>
#include <qstring.h>
#include "../../../core/model.hpp"
@ -13,32 +14,28 @@ namespace qs::i3::ipc {
I3IpcQml::I3IpcQml() {
auto* instance = I3Ipc::instance();
// clang-format off
QObject::connect(instance, &I3Ipc::rawEvent, this, &I3IpcQml::rawEvent);
QObject::connect(instance, &I3Ipc::connected, this, &I3IpcQml::connected);
QObject::connect(
instance,
&I3Ipc::focusedWorkspaceChanged,
this,
&I3IpcQml::focusedWorkspaceChanged
);
QObject::connect(instance, &I3Ipc::focusedWorkspaceChanged, this, &I3IpcQml::focusedWorkspaceChanged);
QObject::connect(instance, &I3Ipc::focusedMonitorChanged, this, &I3IpcQml::focusedMonitorChanged);
// clang-format on
}
void I3IpcQml::dispatch(const QString& request) { I3Ipc::instance()->dispatch(request); }
void I3IpcQml::refreshMonitors() { I3Ipc::instance()->refreshMonitors(); }
void I3IpcQml::refreshWorkspaces() { I3Ipc::instance()->refreshWorkspaces(); }
QString I3IpcQml::socketPath() { return I3Ipc::instance()->socketPath(); }
ObjectModel<I3Monitor>* I3IpcQml::monitors() { return I3Ipc::instance()->monitors(); }
ObjectModel<I3Workspace>* I3IpcQml::workspaces() { return I3Ipc::instance()->workspaces(); }
I3Workspace* I3IpcQml::focusedWorkspace() { return I3Ipc::instance()->focusedWorkspace(); }
QBindable<I3Workspace*> I3IpcQml::bindableFocusedWorkspace() {
return I3Ipc::instance()->bindableFocusedWorkspace();
}
I3Monitor* I3IpcQml::focusedMonitor() { return I3Ipc::instance()->focusedMonitor(); }
QBindable<I3Monitor*> I3IpcQml::bindableFocusedMonitor() {
return I3Ipc::instance()->bindableFocusedMonitor();
}
I3Workspace* I3IpcQml::findWorkspaceByName(const QString& name) {
return I3Ipc::instance()->findWorkspaceByName(name);

View file

@ -2,6 +2,7 @@
#include <qjsonarray.h>
#include <qobject.h>
#include <qproperty.h>
#include "../../../core/doc.hpp"
#include "../../../core/qmlscreen.hpp"
@ -16,8 +17,8 @@ class I3IpcQml: public QObject {
/// Path to the I3 socket
Q_PROPERTY(QString socketPath READ socketPath CONSTANT);
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ focusedWorkspace NOTIFY focusedWorkspaceChanged);
Q_PROPERTY(qs::i3::ipc::I3Monitor* focusedMonitor READ focusedMonitor NOTIFY focusedMonitorChanged);
Q_PROPERTY(qs::i3::ipc::I3Workspace* focusedWorkspace READ default NOTIFY focusedWorkspaceChanged BINDABLE bindableFocusedWorkspace);
Q_PROPERTY(qs::i3::ipc::I3Monitor* focusedMonitor READ default NOTIFY focusedMonitorChanged BINDABLE bindableFocusedMonitor);
/// All I3 monitors.
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::i3::ipc::I3Monitor>*);
Q_PROPERTY(UntypedObjectModel* monitors READ monitors CONSTANT);
@ -59,10 +60,10 @@ public:
[[nodiscard]] static ObjectModel<I3Workspace>* workspaces();
/// The currently focused Workspace
[[nodiscard]] static I3Workspace* focusedWorkspace();
[[nodiscard]] static QBindable<I3Workspace*> bindableFocusedWorkspace();
/// The currently focused Monitor
[[nodiscard]] static I3Monitor* focusedMonitor();
[[nodiscard]] static QBindable<I3Monitor*> bindableFocusedMonitor();
signals:
void rawEvent(I3IpcEvent* event);

View file

@ -1,45 +1,62 @@
#include "workspace.hpp"
#include <qcontainerfwd.h>
#include <qobject.h>
#include <qproperty.h>
#include <qstring.h>
#include <qtmetamacros.h>
#include <qtypes.h>
#include "connection.hpp"
#include "monitor.hpp"
namespace qs::i3::ipc {
I3Monitor* I3Workspace::monitor() const { return this->mMonitor; }
I3Workspace::I3Workspace(I3Ipc* ipc): QObject(ipc), ipc(ipc) {
Qt::beginPropertyUpdateGroup();
this->bActive.setBinding([this]() {
return this->bMonitor.value() && this->bMonitor->bindableActiveWorkspace().value() == this;
});
this->bFocused.setBinding([this]() {
return this->ipc->bindableFocusedWorkspace().value() == this;
});
Qt::endPropertyUpdateGroup();
}
QVariantMap I3Workspace::lastIpcObject() const { return this->mLastIpcObject; }
void I3Workspace::updateFromObject(const QVariantMap& obj) {
Qt::beginPropertyUpdateGroup();
this->bId = obj.value("id").value<qint32>();
this->bName = obj.value("name").value<QString>();
this->bNum = obj.value("num").value<qint32>();
this->bUrgent = obj.value("urgent").value<bool>();
this->bFocused = obj.value("focused").value<bool>();
Qt::endPropertyUpdateGroup();
auto monitorName = obj.value("output").value<QString>();
if (obj != this->mLastIpcObject) {
this->mLastIpcObject = obj;
emit this->lastIpcObjectChanged();
}
if (monitorName != this->mMonitorName) {
Qt::beginPropertyUpdateGroup();
this->bId = obj.value("id").value<qint32>();
this->bName = obj.value("name").value<QString>();
this->bNumber = obj.value("num").value<qint32>();
this->bUrgent = obj.value("urgent").value<bool>();
auto monitorName = obj.value("output").value<QString>();
if (!this->bMonitor || monitorName != this->bMonitor->bindableName().value()) {
auto* monitor = this->ipc->findMonitorByName(monitorName);
if (monitorName.isEmpty() || monitor == nullptr) { // is null when output is disabled
this->mMonitor = nullptr;
this->mMonitorName = "";
this->bMonitor = nullptr;
} else {
this->mMonitorName = monitorName;
this->mMonitor = monitor;
this->bMonitor = monitor;
}
emit this->monitorChanged();
}
Qt::endPropertyUpdateGroup();
}
void I3Workspace::activate() {
this->ipc->dispatch(QString("workspace number %1").arg(this->bNumber.value()));
}
} // namespace qs::i3::ipc

View file

@ -8,45 +8,55 @@
namespace qs::i3::ipc {
class I3Monitor;
///! I3/Sway workspaces
class I3Workspace: public QObject {
Q_OBJECT;
// clang-format off
/// The ID of this workspace, it is unique for i3/Sway launch
Q_PROPERTY(qint32 id READ default NOTIFY idChanged BINDABLE bindableId);
/// The name of this workspace
Q_PROPERTY(QString name READ default NOTIFY nameChanged BINDABLE bindableName);
/// The number of this workspace
Q_PROPERTY(qint32 num READ default NOTIFY numChanged BINDABLE bindableNum);
Q_PROPERTY(qint32 number READ default NOTIFY numberChanged BINDABLE bindableNumber);
/// Deprecated: use @@number
Q_PROPERTY(qint32 num READ default NOTIFY numberChanged BINDABLE bindableNumber);
/// If a window in this workspace has an urgent notification
Q_PROPERTY(bool urgent READ default NOTIFY urgentChanged BINDABLE bindableUrgent);
/// If this workspace is the one currently in focus
/// If this workspace is currently active on its monitor. See also @@focused.
Q_PROPERTY(bool active READ default NOTIFY activeChanged BINDABLE bindableActive);
/// If this workspace is currently active on a monitor and that monitor is currently
/// focused. See also @@active.
Q_PROPERTY(bool focused READ default NOTIFY focusedChanged BINDABLE bindableFocused);
/// The monitor this workspace is being displayed on
Q_PROPERTY(qs::i3::ipc::I3Monitor* monitor READ monitor NOTIFY monitorChanged);
Q_PROPERTY(qs::i3::ipc::I3Monitor* monitor READ default NOTIFY monitorChanged BINDABLE bindableMonitor);
/// Last JSON returned for this workspace, as a JavaScript object.
///
/// This updates every time we receive a `workspace` event from i3/Sway
Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
// clang-format on
QML_ELEMENT;
QML_UNCREATABLE("I3Workspaces must be retrieved from the I3 object.");
public:
I3Workspace(qs::i3::ipc::I3Ipc* ipc): QObject(ipc), ipc(ipc) {}
I3Workspace(I3Ipc* ipc);
/// Activate the workspace.
///
/// > [!NOTE] This is equivalent to running
/// > ```qml
/// > I3.dispatch(`workspace number ${workspace.number}`);
/// > ```
Q_INVOKABLE void activate();
[[nodiscard]] QBindable<qint32> bindableId() { return &this->bId; }
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
[[nodiscard]] QBindable<qint32> bindableNum() { return &this->bNum; }
[[nodiscard]] QBindable<qint32> bindableNumber() { return &this->bNumber; }
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
[[nodiscard]] I3Monitor* monitor() const;
[[nodiscard]] QBindable<I3Monitor*> bindableMonitor() { return &this->bMonitor; }
[[nodiscard]] QVariantMap lastIpcObject() const;
void updateFromObject(const QVariantMap& obj);
@ -55,8 +65,9 @@ signals:
void idChanged();
void nameChanged();
void urgentChanged();
void activeChanged();
void focusedChanged();
void numChanged();
void numberChanged();
void monitorChanged();
void lastIpcObjectChanged();
@ -64,13 +75,15 @@ private:
I3Ipc* ipc;
QVariantMap mLastIpcObject;
I3Monitor* mMonitor = nullptr;
QString mMonitorName;
// clang-format off
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(I3Workspace, qint32, bId, -1, &I3Workspace::idChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Workspace, QString, bName, &I3Workspace::nameChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(I3Workspace, qint32, bNum, -1, &I3Workspace::numChanged);
Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(I3Workspace, qint32, bNumber, -1, &I3Workspace::numberChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Workspace, bool, bActive, &I3Workspace::activeChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Workspace, bool, bFocused, &I3Workspace::focusedChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Workspace, bool, bUrgent, &I3Workspace::urgentChanged);
Q_OBJECT_BINDABLE_PROPERTY(I3Workspace, I3Monitor*, bMonitor, &I3Workspace::monitorChanged);
// clang-format on
};
} // namespace qs::i3::ipc