From 62ccab5d3043a3256b2ab68054add1b8a2f6857e Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Wed, 26 Mar 2025 02:52:23 -0700 Subject: [PATCH] hyprland/ipc: expose active and focused properties + activate() --- src/wayland/hyprland/ipc/connection.cpp | 24 ++++++++++---------- src/wayland/hyprland/ipc/connection.hpp | 17 ++++++++++++--- src/wayland/hyprland/ipc/monitor.cpp | 29 ++++++++++++++----------- src/wayland/hyprland/ipc/monitor.hpp | 16 +++++++++++--- src/wayland/hyprland/ipc/qml.cpp | 2 +- src/wayland/hyprland/ipc/workspace.cpp | 18 +++++++++++++++ src/wayland/hyprland/ipc/workspace.hpp | 19 ++++++++++++++++ 7 files changed, 92 insertions(+), 33 deletions(-) diff --git a/src/wayland/hyprland/ipc/connection.cpp b/src/wayland/hyprland/ipc/connection.cpp index fb1cbc9f..993f3bcf 100644 --- a/src/wayland/hyprland/ipc/connection.cpp +++ b/src/wayland/hyprland/ipc/connection.cpp @@ -314,7 +314,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { delete workspace; for (auto* monitor: this->mMonitors.valueList()) { - if (monitor->activeWorkspace() == nullptr) { + if (monitor->bindableActiveWorkspace().value() == nullptr) { // removing a monitor will cause a new workspace to be created and destroyed after removal, // but it won't go back to a real workspace afterwards and just leaves a null, so we // re-query monitors if this appears to be the case. @@ -342,11 +342,11 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) { auto id = args.at(0).toInt(); auto name = QString::fromUtf8(args.at(1)); - if (this->mFocusedMonitor != nullptr) { + if (this->bFocusedMonitor != nullptr) { auto* workspace = this->findWorkspaceByName(name, true, id); - this->mFocusedMonitor->setActiveWorkspace(workspace); + this->bFocusedMonitor->setActiveWorkspace(workspace); qCDebug(logHyprlandIpc) << "Workspace" << id << "activated on" - << this->mFocusedMonitor->bindableName().value(); + << this->bFocusedMonitor->bindableName().value(); } } else if (event->name == "moveworkspacev2") { auto args = event->parseView(3); @@ -504,8 +504,6 @@ HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 } } -HyprlandMonitor* HyprlandIpc::focusedMonitor() const { return this->mFocusedMonitor; } - HyprlandMonitor* HyprlandIpc::monitorFor(QuickshellScreenInfo* screen) { // Wayland monitors appear after hyprland ones are created and disappear after destruction // so simply not doing any preemptive creation is enough, however if this call creates @@ -517,22 +515,22 @@ HyprlandMonitor* HyprlandIpc::monitorFor(QuickshellScreenInfo* screen) { } void HyprlandIpc::setFocusedMonitor(HyprlandMonitor* monitor) { - if (monitor == this->mFocusedMonitor) return; + auto* oldMonitor = this->bFocusedMonitor.value(); + if (monitor == oldMonitor) return; - if (this->mFocusedMonitor != nullptr) { - QObject::disconnect(this->mFocusedMonitor, nullptr, this, nullptr); + if (this->bFocusedMonitor != nullptr) { + QObject::disconnect(this->bFocusedMonitor, nullptr, this, nullptr); } - this->mFocusedMonitor = monitor; - if (monitor != nullptr) { QObject::connect(monitor, &QObject::destroyed, this, &HyprlandIpc::onFocusedMonitorDestroyed); } - emit this->focusedMonitorChanged(); + + this->bFocusedMonitor = monitor; } void HyprlandIpc::onFocusedMonitorDestroyed() { - this->mFocusedMonitor = nullptr; + this->bFocusedMonitor = nullptr; emit this->focusedMonitorChanged(); } diff --git a/src/wayland/hyprland/ipc/connection.hpp b/src/wayland/hyprland/ipc/connection.hpp index 287b1ee8..e7c981f4 100644 --- a/src/wayland/hyprland/ipc/connection.hpp +++ b/src/wayland/hyprland/ipc/connection.hpp @@ -7,8 +7,10 @@ #include #include #include +#include #include #include +#include #include "../../../core/model.hpp" #include "../../../core/qmlscreen.hpp" @@ -74,7 +76,11 @@ public: void dispatch(const QString& request); [[nodiscard]] HyprlandMonitor* monitorFor(QuickshellScreenInfo* screen); - [[nodiscard]] HyprlandMonitor* focusedMonitor() const; + + [[nodiscard]] QBindable bindableFocusedMonitor() const { + return &this->bFocusedMonitor; + } + void setFocusedMonitor(HyprlandMonitor* monitor); [[nodiscard]] ObjectModel* monitors(); @@ -119,10 +125,15 @@ private: ObjectModel mMonitors {this}; ObjectModel mWorkspaces {this}; - HyprlandMonitor* mFocusedMonitor = nullptr; - //HyprlandWorkspace* activeWorkspace = nullptr; HyprlandIpcEvent event {this}; + + Q_OBJECT_BINDABLE_PROPERTY( + HyprlandIpc, + HyprlandMonitor*, + bFocusedMonitor, + &HyprlandIpc::focusedMonitorChanged + ); }; } // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/monitor.cpp b/src/wayland/hyprland/ipc/monitor.cpp index 68312674..ddf37067 100644 --- a/src/wayland/hyprland/ipc/monitor.cpp +++ b/src/wayland/hyprland/ipc/monitor.cpp @@ -18,6 +18,11 @@ void HyprlandMonitor::updateInitial(qint32 id, const QString& name, const QStrin this->bId = id; this->bName = name; this->bDescription = description; + + this->bFocused.setBinding([this]() { + return HyprlandIpc::instance()->bindableFocusedMonitor().value() == this; + }); + Qt::endPropertyUpdateGroup(); } @@ -38,8 +43,8 @@ void HyprlandMonitor::updateFromObject(QVariantMap object) { this->bScale = object.value("scale").value(); Qt::endPropertyUpdateGroup(); - if (this->mActiveWorkspace == nullptr - || this->mActiveWorkspace->bindableName().value() != activeWorkspaceName) + if (this->bActiveWorkspace == nullptr + || this->bActiveWorkspace->bindableName().value() != activeWorkspaceName) { auto* workspace = this->ipc->findWorkspaceByName(activeWorkspaceName, true, activeWorkspaceId); workspace->setMonitor(this); @@ -54,16 +59,15 @@ void HyprlandMonitor::updateFromObject(QVariantMap object) { } } -HyprlandWorkspace* HyprlandMonitor::activeWorkspace() const { return this->mActiveWorkspace; } - void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) { - if (workspace == this->mActiveWorkspace) return; + auto* oldWorkspace = this->bActiveWorkspace.value(); + if (workspace == oldWorkspace) return; - if (this->mActiveWorkspace != nullptr) { - QObject::disconnect(this->mActiveWorkspace, nullptr, this, nullptr); + if (oldWorkspace != nullptr) { + QObject::disconnect(oldWorkspace, nullptr, this, nullptr); } - this->mActiveWorkspace = workspace; + Qt::beginPropertyUpdateGroup(); if (workspace != nullptr) { workspace->setMonitor(this); @@ -76,12 +80,11 @@ void HyprlandMonitor::setActiveWorkspace(HyprlandWorkspace* workspace) { ); } - emit this->activeWorkspaceChanged(); + this->bActiveWorkspace = workspace; + + Qt::endPropertyUpdateGroup(); } -void HyprlandMonitor::onActiveWorkspaceDestroyed() { - this->mActiveWorkspace = nullptr; - emit this->activeWorkspaceChanged(); -} +void HyprlandMonitor::onActiveWorkspaceDestroyed() { this->bActiveWorkspace = nullptr; } } // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/monitor.hpp b/src/wayland/hyprland/ipc/monitor.hpp index 974fcbfc..83481d4b 100644 --- a/src/wayland/hyprland/ipc/monitor.hpp +++ b/src/wayland/hyprland/ipc/monitor.hpp @@ -9,6 +9,7 @@ #include #include "connection.hpp" +#include "workspace.hpp" namespace qs::hyprland::ipc { @@ -30,7 +31,9 @@ class HyprlandMonitor: public QObject { /// > property, run @@Hyprland.refreshMonitors() and wait for this property to update. Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged); /// The currently active workspace on this monitor. May be null. - Q_PROPERTY(qs::hyprland::ipc::HyprlandWorkspace* activeWorkspace READ activeWorkspace NOTIFY activeWorkspaceChanged); + Q_PROPERTY(qs::hyprland::ipc::HyprlandWorkspace* activeWorkspace READ default NOTIFY activeWorkspaceChanged); + /// If the monitor is currently focused. + Q_PROPERTY(bool focused READ default NOTIFY focusedChanged); // clang-format on QML_ELEMENT; QML_UNCREATABLE("HyprlandMonitors must be retrieved from the HyprlandIpc object."); @@ -50,10 +53,15 @@ public: [[nodiscard]] QBindable bindableHeight() { return &this->bHeight; } [[nodiscard]] QBindable bindableScale() { return &this->bScale; } + [[nodiscard]] QBindable bindableActiveWorkspace() const { + return &this->bActiveWorkspace; + } + + [[nodiscard]] QBindable bindableFocused() const { return &this->bFocused; } + [[nodiscard]] QVariantMap lastIpcObject() const; void setActiveWorkspace(HyprlandWorkspace* workspace); - [[nodiscard]] HyprlandWorkspace* activeWorkspace() const; signals: void idChanged(); @@ -66,6 +74,7 @@ signals: void scaleChanged(); void lastIpcObjectChanged(); void activeWorkspaceChanged(); + void focusedChanged(); private slots: void onActiveWorkspaceDestroyed(); @@ -74,7 +83,6 @@ private: HyprlandIpc* ipc; QVariantMap mLastIpcObject; - HyprlandWorkspace* mActiveWorkspace = nullptr; // clang-format off Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(HyprlandMonitor, qint32, bId, -1, &HyprlandMonitor::idChanged); @@ -85,6 +93,8 @@ private: Q_OBJECT_BINDABLE_PROPERTY(HyprlandMonitor, qint32, bWidth, &HyprlandMonitor::widthChanged); Q_OBJECT_BINDABLE_PROPERTY(HyprlandMonitor, qint32, bHeight, &HyprlandMonitor::heightChanged); Q_OBJECT_BINDABLE_PROPERTY(HyprlandMonitor, qreal, bScale, &HyprlandMonitor::scaleChanged); + Q_OBJECT_BINDABLE_PROPERTY(HyprlandMonitor, HyprlandWorkspace*, bActiveWorkspace, &HyprlandMonitor::activeWorkspaceChanged); + Q_OBJECT_BINDABLE_PROPERTY(HyprlandMonitor, bool, bFocused, &HyprlandMonitor::focusedChanged); // clang-format on }; diff --git a/src/wayland/hyprland/ipc/qml.cpp b/src/wayland/hyprland/ipc/qml.cpp index 1e75ee9c..35bf4eab 100644 --- a/src/wayland/hyprland/ipc/qml.cpp +++ b/src/wayland/hyprland/ipc/qml.cpp @@ -38,7 +38,7 @@ QString HyprlandIpcQml::requestSocketPath() { return HyprlandIpc::instance()->re QString HyprlandIpcQml::eventSocketPath() { return HyprlandIpc::instance()->eventSocketPath(); } HyprlandMonitor* HyprlandIpcQml::focusedMonitor() { - return HyprlandIpc::instance()->focusedMonitor(); + return HyprlandIpc::instance()->bindableFocusedMonitor().value(); } ObjectModel* HyprlandIpcQml::monitors() { diff --git a/src/wayland/hyprland/ipc/workspace.cpp b/src/wayland/hyprland/ipc/workspace.cpp index f0cda668..e7aa93dd 100644 --- a/src/wayland/hyprland/ipc/workspace.cpp +++ b/src/wayland/hyprland/ipc/workspace.cpp @@ -59,10 +59,24 @@ void HyprlandWorkspace::setMonitor(HyprlandMonitor* monitor) { this->mMonitor = monitor; + Qt::beginPropertyUpdateGroup(); + if (monitor != nullptr) { QObject::connect(monitor, &QObject::destroyed, this, &HyprlandWorkspace::onMonitorDestroyed); + + this->bActive.setBinding([this]() { + return this->mMonitor->bindableActiveWorkspace().value() == this; + }); + + this->bFocused.setBinding([this]() { + return this->bActive.value() && this->mMonitor->bindableFocused().value(); + }); + } else { + this->bActive = false; + this->bFocused = false; } + Qt::endPropertyUpdateGroup(); emit this->monitorChanged(); } @@ -71,4 +85,8 @@ void HyprlandWorkspace::onMonitorDestroyed() { emit this->monitorChanged(); } +void HyprlandWorkspace::activate() { + HyprlandIpc::instance()->dispatch(QString("workspace %1").arg(this->bId.value())); +} + } // namespace qs::hyprland::ipc diff --git a/src/wayland/hyprland/ipc/workspace.hpp b/src/wayland/hyprland/ipc/workspace.hpp index 51afcad1..d7c0c0e4 100644 --- a/src/wayland/hyprland/ipc/workspace.hpp +++ b/src/wayland/hyprland/ipc/workspace.hpp @@ -16,6 +16,11 @@ class HyprlandWorkspace: public QObject { Q_OBJECT; 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. + 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); /// Last json returned for this workspace, as a javascript object. /// /// > [!WARNING] This is *not* updated unless the workspace object is fetched again from @@ -32,8 +37,18 @@ public: void updateInitial(qint32 id, const QString& name); void updateFromObject(QVariantMap object); + /// Activate the workspace. + /// + /// > [!NOTE] This is equivalent to running + /// > ```qml + /// > HyprlandIpc.dispatch(`workspace ${workspace.id}`); + /// > ``` + Q_INVOKABLE void activate(); + [[nodiscard]] QBindable bindableId() { return &this->bId; } [[nodiscard]] QBindable bindableName() { return &this->bName; } + [[nodiscard]] QBindable bindableActive() { return &this->bActive; } + [[nodiscard]] QBindable bindableFocused() { return &this->bFocused; } [[nodiscard]] QVariantMap lastIpcObject() const; @@ -43,6 +58,8 @@ public: signals: void idChanged(); void nameChanged(); + void activeChanged(); + void focusedChanged(); void lastIpcObjectChanged(); void monitorChanged(); @@ -58,6 +75,8 @@ private: // clang-format off 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); + Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bFocused, &HyprlandWorkspace::focusedChanged); // clang-format on };