hyprland/ipc: expose Hyprland toplevels
This commit is contained in:
parent
c115df8d34
commit
362c8e1b69
11 changed files with 685 additions and 43 deletions
|
@ -14,6 +14,8 @@
|
|||
#include <qlogging.h>
|
||||
#include <qloggingcategory.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqml.h>
|
||||
#include <qtenvironmentvariables.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
@ -21,7 +23,10 @@
|
|||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../toplevel_management/handle.hpp"
|
||||
#include "hyprland_toplevel.hpp"
|
||||
#include "monitor.hpp"
|
||||
#include "toplevel_mapping.hpp"
|
||||
#include "workspace.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
@ -62,11 +67,16 @@ HyprlandIpc::HyprlandIpc() {
|
|||
QObject::connect(&this->eventSocket, &QLocalSocket::errorOccurred, this, &HyprlandIpc::eventSocketError);
|
||||
QObject::connect(&this->eventSocket, &QLocalSocket::stateChanged, this, &HyprlandIpc::eventSocketStateChanged);
|
||||
QObject::connect(&this->eventSocket, &QLocalSocket::readyRead, this, &HyprlandIpc::eventSocketReady);
|
||||
|
||||
auto *instance = HyprlandToplevelMappingManager::instance();
|
||||
QObject::connect(instance, &HyprlandToplevelMappingManager::toplevelAddressed, this, &HyprlandIpc::toplevelAddressed);
|
||||
|
||||
// clang-format on
|
||||
|
||||
this->eventSocket.connectToServer(this->mEventSocketPath, QLocalSocket::ReadOnly);
|
||||
this->refreshMonitors(true);
|
||||
this->refreshWorkspaces(true);
|
||||
this->refreshToplevels();
|
||||
}
|
||||
|
||||
QString HyprlandIpc::requestSocketPath() const { return this->mRequestSocketPath; }
|
||||
|
@ -113,6 +123,36 @@ void HyprlandIpc::eventSocketReady() {
|
|||
}
|
||||
}
|
||||
|
||||
void HyprlandIpc::toplevelAddressed(
|
||||
wayland::toplevel_management::impl::ToplevelHandle* handle,
|
||||
quint64 address
|
||||
) {
|
||||
auto* waylandToplevel =
|
||||
wayland::toplevel_management::ToplevelManager::instance()->forImpl(handle);
|
||||
|
||||
if (!waylandToplevel) return;
|
||||
|
||||
auto* attached = qobject_cast<HyprlandToplevel*>(
|
||||
qmlAttachedPropertiesObject<HyprlandToplevel>(waylandToplevel, false)
|
||||
);
|
||||
|
||||
auto* hyprToplevel = this->findToplevelByAddress(address, true);
|
||||
|
||||
if (attached) {
|
||||
if (attached->address()) {
|
||||
qCDebug(logHyprlandIpc) << "Toplevel" << attached->addressStr() << "already has address"
|
||||
<< address;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
attached->setAddress(address);
|
||||
attached->setHyprlandHandle(hyprToplevel);
|
||||
}
|
||||
|
||||
hyprToplevel->setWaylandHandle(waylandToplevel->implHandle());
|
||||
}
|
||||
|
||||
void HyprlandIpc::makeRequest(
|
||||
const QByteArray& request,
|
||||
const std::function<void(bool, QByteArray)>& callback
|
||||
|
@ -166,6 +206,8 @@ ObjectModel<HyprlandMonitor>* HyprlandIpc::monitors() { return &this->mMonitors;
|
|||
|
||||
ObjectModel<HyprlandWorkspace>* HyprlandIpc::workspaces() { return &this->mWorkspaces; }
|
||||
|
||||
ObjectModel<HyprlandToplevel>* HyprlandIpc::toplevels() { return &this->mToplevels; }
|
||||
|
||||
QVector<QByteArrayView> HyprlandIpc::parseEventArgs(QByteArrayView event, quint16 count) {
|
||||
auto args = QVector<QByteArrayView>();
|
||||
|
||||
|
@ -218,6 +260,7 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
|||
if (event->name == "configreloaded") {
|
||||
this->refreshMonitors(true);
|
||||
this->refreshWorkspaces(true);
|
||||
this->refreshToplevels();
|
||||
} else if (event->name == "monitoraddedv2") {
|
||||
auto args = event->parseView(3);
|
||||
|
||||
|
@ -390,6 +433,133 @@ void HyprlandIpc::onEvent(HyprlandIpcEvent* event) {
|
|||
// the fullscreen state changed, but this falls apart if you move a fullscreen
|
||||
// window between workspaces.
|
||||
this->refreshWorkspaces(false);
|
||||
} else if (event->name == "openwindow") {
|
||||
auto args = event->parseView(4);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
auto workspaceName = QString::fromUtf8(args.at(1));
|
||||
auto windowTitle = QString::fromUtf8(args.at(2));
|
||||
auto windowClass = QString::fromUtf8(args.at(3));
|
||||
|
||||
auto* workspace = this->findWorkspaceByName(workspaceName, false);
|
||||
if (!workspace) {
|
||||
qCWarning(logHyprlandIpc) << "Got openwindow for workspace" << workspaceName
|
||||
<< "which was not previously tracked.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* toplevel = this->findToplevelByAddress(windowAddress, false);
|
||||
const bool existed = toplevel != nullptr;
|
||||
|
||||
if (!toplevel) toplevel = new HyprlandToplevel(this);
|
||||
toplevel->updateInitial(windowAddress, windowTitle, workspaceName);
|
||||
|
||||
workspace->insertToplevel(toplevel);
|
||||
|
||||
if (!existed) {
|
||||
this->mToplevels.insertObject(toplevel);
|
||||
qCDebug(logHyprlandIpc) << "New toplevel created with address" << windowAddress << ", title"
|
||||
<< windowTitle << ", workspace" << workspaceName;
|
||||
}
|
||||
} else if (event->name == "closewindow") {
|
||||
auto args = event->parseView(1);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
auto toplevelIter = std::ranges::find_if(mList, [windowAddress](HyprlandToplevel* m) {
|
||||
return m->address() == windowAddress;
|
||||
});
|
||||
|
||||
if (toplevelIter == mList.end()) {
|
||||
qCWarning(logHyprlandIpc) << "Got closewindow for address" << windowAddress
|
||||
<< "which was not previously tracked.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* toplevel = *toplevelIter;
|
||||
auto index = toplevelIter - mList.begin();
|
||||
this->mToplevels.removeAt(index);
|
||||
|
||||
// Remove from workspace
|
||||
auto* workspace = toplevel->bindableWorkspace().value();
|
||||
if (workspace) {
|
||||
workspace->toplevels()->removeObject(toplevel);
|
||||
}
|
||||
|
||||
delete toplevel;
|
||||
} else if (event->name == "movewindowv2") {
|
||||
auto args = event->parseView(3);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
auto workspaceName = QString::fromUtf8(args.at(2));
|
||||
|
||||
auto* toplevel = this->findToplevelByAddress(windowAddress, false);
|
||||
if (!toplevel) {
|
||||
qCWarning(logHyprlandIpc) << "Got movewindowv2 event for client with address" << windowAddress
|
||||
<< "which was not previously tracked.";
|
||||
return;
|
||||
}
|
||||
|
||||
HyprlandWorkspace* workspace = this->findWorkspaceByName(workspaceName, false);
|
||||
if (!workspace) {
|
||||
qCWarning(logHyprlandIpc) << "Got movewindowv2 event for workspace" << args.at(2)
|
||||
<< "which was not previously tracked.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto* oldWorkspace = toplevel->bindableWorkspace().value();
|
||||
toplevel->setWorkspace(workspace);
|
||||
|
||||
if (oldWorkspace) {
|
||||
oldWorkspace->removeToplevel(toplevel);
|
||||
}
|
||||
|
||||
workspace->insertToplevel(toplevel);
|
||||
} else if (event->name == "windowtitlev2") {
|
||||
auto args = event->parseView(2);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
auto windowTitle = QString::fromUtf8(args.at(1));
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
// It happens that Hyprland sends windowtitlev2 events before event
|
||||
// "openwindow" is emitted, so let's preemptively create it
|
||||
auto* toplevel = this->findToplevelByAddress(windowAddress, true);
|
||||
if (!toplevel) {
|
||||
qCWarning(logHyprlandIpc) << "Got windowtitlev2 event for client with address"
|
||||
<< windowAddress << "which was not previously tracked.";
|
||||
return;
|
||||
}
|
||||
|
||||
toplevel->bindableTitle().setValue(windowTitle);
|
||||
} else if (event->name == "activewindowv2") {
|
||||
auto args = event->parseView(1);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
// Did not observe "activewindowv2" event before "openwindow",
|
||||
// but better safe than sorry, so create if missing.
|
||||
auto* toplevel = this->findToplevelByAddress(windowAddress, true);
|
||||
this->bActiveToplevel = toplevel;
|
||||
} else if (event->name == "urgent") {
|
||||
auto args = event->parseView(1);
|
||||
auto ok = false;
|
||||
auto windowAddress = args.at(0).toULongLong(&ok, 16);
|
||||
|
||||
if (!ok) return;
|
||||
|
||||
// It happens that Hyprland sends urgent before "openwindow"
|
||||
auto* toplevel = this->findToplevelByAddress(windowAddress, true);
|
||||
toplevel->bindableUrgent().setValue(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -496,6 +666,71 @@ void HyprlandIpc::refreshWorkspaces(bool canCreate) {
|
|||
});
|
||||
}
|
||||
|
||||
HyprlandToplevel* HyprlandIpc::findToplevelByAddress(quint64 address, bool createIfMissing) {
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
HyprlandToplevel* toplevel = nullptr;
|
||||
|
||||
auto toplevelIter =
|
||||
std::ranges::find_if(mList, [&](HyprlandToplevel* m) { return m->address() == address; });
|
||||
|
||||
toplevel = toplevelIter == mList.end() ? nullptr : *toplevelIter;
|
||||
|
||||
if (!toplevel && createIfMissing) {
|
||||
qCDebug(logHyprlandIpc) << "Toplevel with address" << address
|
||||
<< "requested before creation, performing early init";
|
||||
|
||||
toplevel = new HyprlandToplevel(this);
|
||||
toplevel->updateInitial(address, "", "");
|
||||
this->mToplevels.insertObject(toplevel);
|
||||
}
|
||||
|
||||
return toplevel;
|
||||
}
|
||||
|
||||
void HyprlandIpc::refreshToplevels() {
|
||||
if (this->requestingToplevels) return;
|
||||
this->requestingToplevels = true;
|
||||
|
||||
this->makeRequest("j/clients", [this](bool success, const QByteArray& resp) {
|
||||
this->requestingToplevels = false;
|
||||
if (!success) return;
|
||||
|
||||
qCDebug(logHyprlandIpc) << "Parsing j/clients response";
|
||||
auto json = QJsonDocument::fromJson(resp).array();
|
||||
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
|
||||
for (auto entry: json) {
|
||||
auto object = entry.toObject().toVariantMap();
|
||||
|
||||
bool ok = false;
|
||||
auto address = object.value("address").toString().toULongLong(&ok, 16);
|
||||
|
||||
if (!ok) {
|
||||
qCWarning(logHyprlandIpc) << "Invalid address in j/clients entry:" << object;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto toplevelsIter =
|
||||
std::ranges::find_if(mList, [&](HyprlandToplevel* m) { return m->address() == address; });
|
||||
|
||||
auto* toplevel = toplevelsIter == mList.end() ? nullptr : *toplevelsIter;
|
||||
auto exists = toplevel != nullptr;
|
||||
|
||||
if (!exists) toplevel = new HyprlandToplevel(this);
|
||||
toplevel->updateFromObject(object);
|
||||
|
||||
if (!exists) {
|
||||
qCDebug(logHyprlandIpc) << "New toplevel created with address" << address;
|
||||
this->mToplevels.insertObject(toplevel);
|
||||
}
|
||||
|
||||
auto* workspace = toplevel->bindableWorkspace().value();
|
||||
workspace->insertToplevel(toplevel);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HyprlandMonitor*
|
||||
HyprlandIpc::findMonitorByName(const QString& name, bool createIfMissing, qint32 id) {
|
||||
const auto& mList = this->mMonitors.valueList();
|
||||
|
|
|
@ -14,16 +14,19 @@
|
|||
|
||||
#include "../../../core/model.hpp"
|
||||
#include "../../../core/qmlscreen.hpp"
|
||||
#include "../../../wayland/toplevel_management/handle.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
class HyprlandMonitor;
|
||||
class HyprlandWorkspace;
|
||||
class HyprlandToplevel;
|
||||
|
||||
} // namespace qs::hyprland::ipc
|
||||
|
||||
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandWorkspace*);
|
||||
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandMonitor*);
|
||||
Q_DECLARE_OPAQUE_POINTER(qs::hyprland::ipc::HyprlandToplevel*);
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
|
@ -85,18 +88,25 @@ public:
|
|||
return &this->bFocusedWorkspace;
|
||||
}
|
||||
|
||||
[[nodiscard]] QBindable<HyprlandToplevel*> bindableActiveToplevel() const {
|
||||
return &this->bActiveToplevel;
|
||||
}
|
||||
|
||||
void setFocusedMonitor(HyprlandMonitor* monitor);
|
||||
|
||||
[[nodiscard]] ObjectModel<HyprlandMonitor>* monitors();
|
||||
[[nodiscard]] ObjectModel<HyprlandWorkspace>* workspaces();
|
||||
[[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);
|
||||
HyprlandMonitor* findMonitorByName(const QString& name, bool createIfMissing, qint32 id = -1);
|
||||
HyprlandToplevel* findToplevelByAddress(quint64 address, bool createIfMissing);
|
||||
|
||||
// canCreate avoids making ghost workspaces when the connection races
|
||||
void refreshWorkspaces(bool canCreate);
|
||||
void refreshMonitors(bool canCreate);
|
||||
void refreshToplevels();
|
||||
|
||||
// The last argument may contain commas, so the count is required.
|
||||
[[nodiscard]] static QVector<QByteArrayView> parseEventArgs(QByteArrayView event, quint16 count);
|
||||
|
@ -107,12 +117,18 @@ signals:
|
|||
|
||||
void focusedMonitorChanged();
|
||||
void focusedWorkspaceChanged();
|
||||
void activeToplevelChanged();
|
||||
|
||||
private slots:
|
||||
void eventSocketError(QLocalSocket::LocalSocketError error) const;
|
||||
void eventSocketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void eventSocketReady();
|
||||
|
||||
void toplevelAddressed(
|
||||
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
|
||||
quint64 address
|
||||
);
|
||||
|
||||
void onFocusedMonitorDestroyed();
|
||||
|
||||
private:
|
||||
|
@ -128,10 +144,12 @@ private:
|
|||
bool valid = false;
|
||||
bool requestingMonitors = false;
|
||||
bool requestingWorkspaces = false;
|
||||
bool requestingToplevels = false;
|
||||
bool monitorsRequested = false;
|
||||
|
||||
ObjectModel<HyprlandMonitor> mMonitors {this};
|
||||
ObjectModel<HyprlandWorkspace> mWorkspaces {this};
|
||||
ObjectModel<HyprlandToplevel> mToplevels {this};
|
||||
|
||||
HyprlandIpcEvent event {this};
|
||||
|
||||
|
@ -148,6 +166,13 @@ private:
|
|||
bFocusedWorkspace,
|
||||
&HyprlandIpc::focusedWorkspaceChanged
|
||||
);
|
||||
|
||||
Q_OBJECT_BINDABLE_PROPERTY(
|
||||
HyprlandIpc,
|
||||
HyprlandToplevel*,
|
||||
bActiveToplevel,
|
||||
&HyprlandIpc::activeToplevelChanged
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace qs::hyprland::ipc
|
||||
|
|
|
@ -2,50 +2,159 @@
|
|||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "toplevel_mapping.hpp"
|
||||
#include "../../toplevel_management/handle.hpp"
|
||||
#include "../../toplevel_management/qml.hpp"
|
||||
#include "connection.hpp"
|
||||
#include "toplevel_mapping.hpp"
|
||||
#include "workspace.hpp"
|
||||
|
||||
using namespace qs::wayland::toplevel_management;
|
||||
using namespace qs::wayland::toplevel_management::impl;
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
HyprlandToplevel::HyprlandToplevel(Toplevel* toplevel)
|
||||
: QObject(toplevel)
|
||||
, handle(toplevel->implHandle()) {
|
||||
auto* instance = HyprlandToplevelMappingManager::instance();
|
||||
auto addr = instance->getToplevelAddress(handle);
|
||||
HyprlandToplevel::HyprlandToplevel(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
|
||||
this->bMonitor.setBinding([this]() {
|
||||
return this->bWorkspace ? this->bWorkspace->bindableMonitor().value() : nullptr;
|
||||
});
|
||||
|
||||
if (addr != 0) this->setAddress(addr);
|
||||
else {
|
||||
QObject::connect(
|
||||
instance,
|
||||
&HyprlandToplevelMappingManager::toplevelAddressed,
|
||||
this,
|
||||
&HyprlandToplevel::onToplevelAddressed
|
||||
);
|
||||
}
|
||||
this->bActivated.setBinding([this]() {
|
||||
return this->ipc->bindableActiveToplevel().value() == this;
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
this,
|
||||
&HyprlandToplevel::activatedChanged,
|
||||
this,
|
||||
&HyprlandToplevel::onActivatedChanged
|
||||
);
|
||||
}
|
||||
|
||||
void HyprlandToplevel::onToplevelAddressed(ToplevelHandle* handle, quint64 address) {
|
||||
if (handle == this->handle) {
|
||||
this->setAddress(address);
|
||||
QObject::disconnect(HyprlandToplevelMappingManager::instance(), nullptr, this, nullptr);
|
||||
HyprlandToplevel::HyprlandToplevel(HyprlandIpc* ipc, Toplevel* toplevel): HyprlandToplevel(ipc) {
|
||||
this->mWaylandHandle = toplevel->implHandle();
|
||||
auto* instance = HyprlandToplevelMappingManager::instance();
|
||||
auto addr = instance->getToplevelAddress(this->mWaylandHandle);
|
||||
|
||||
if (!addr) {
|
||||
// Address not available, will rely on HyprlandIpc to resolve it.
|
||||
return;
|
||||
}
|
||||
|
||||
this->setAddress(addr);
|
||||
|
||||
// Check if client is present in HyprlandIPC
|
||||
auto* hyprToplevel = ipc->findToplevelByAddress(addr, false);
|
||||
// HyprlandIpc will eventually resolve it
|
||||
if (!hyprToplevel) return;
|
||||
|
||||
this->setHyprlandHandle(hyprToplevel);
|
||||
}
|
||||
|
||||
void HyprlandToplevel::updateInitial(
|
||||
quint64 address,
|
||||
const QString& title,
|
||||
const QString& workspaceName
|
||||
) {
|
||||
auto* workspace = this->ipc->findWorkspaceByName(workspaceName, false);
|
||||
Qt::beginPropertyUpdateGroup();
|
||||
this->setAddress(address);
|
||||
this->bTitle = title;
|
||||
this->setWorkspace(workspace);
|
||||
Qt::endPropertyUpdateGroup();
|
||||
}
|
||||
|
||||
void HyprlandToplevel::updateFromObject(const QVariantMap& object) {
|
||||
auto addressStr = object.value("address").value<QString>();
|
||||
auto title = object.value("title").value<QString>();
|
||||
|
||||
bool ok = false;
|
||||
auto address = addressStr.toULongLong(&ok, 16);
|
||||
if (!ok || !address) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->setAddress(address);
|
||||
this->bTitle = title;
|
||||
|
||||
auto workspaceMap = object.value("workspace").toMap();
|
||||
auto workspaceName = workspaceMap.value("name").toString();
|
||||
|
||||
auto* workspace = this->ipc->findWorkspaceByName(workspaceName, false);
|
||||
if (!workspace) return;
|
||||
|
||||
this->setWorkspace(workspace);
|
||||
}
|
||||
|
||||
void HyprlandToplevel::setWorkspace(HyprlandWorkspace* workspace) {
|
||||
auto* oldWorkspace = this->bWorkspace.value();
|
||||
if (oldWorkspace == workspace) return;
|
||||
|
||||
if (oldWorkspace) {
|
||||
QObject::disconnect(oldWorkspace, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->bWorkspace = workspace;
|
||||
|
||||
if (workspace) {
|
||||
QObject::connect(workspace, &QObject::destroyed, this, [this]() {
|
||||
this->bWorkspace = nullptr;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandToplevel::setAddress(quint64 address) {
|
||||
this->mAddress = QString::number(address, 16);
|
||||
this->mAddress = address;
|
||||
emit this->addressChanged();
|
||||
}
|
||||
|
||||
Toplevel* HyprlandToplevel::waylandHandle() {
|
||||
return ToplevelManager::instance()->forImpl(this->mWaylandHandle);
|
||||
}
|
||||
|
||||
void HyprlandToplevel::setWaylandHandle(impl::ToplevelHandle* handle) {
|
||||
if (this->mWaylandHandle == handle) return;
|
||||
if (this->mWaylandHandle) {
|
||||
QObject::disconnect(this->mWaylandHandle, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
this->mWaylandHandle = handle;
|
||||
if (handle) {
|
||||
QObject::connect(handle, &QObject::destroyed, this, [this]() {
|
||||
this->mWaylandHandle = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
emit this->waylandHandleChanged();
|
||||
}
|
||||
|
||||
void HyprlandToplevel::setHyprlandHandle(HyprlandToplevel* handle) {
|
||||
if (this->mHyprlandHandle == handle) return;
|
||||
if (this->mHyprlandHandle) {
|
||||
QObject::disconnect(this->mHyprlandHandle, nullptr, this, nullptr);
|
||||
}
|
||||
this->mHyprlandHandle = handle;
|
||||
if (handle) {
|
||||
QObject::connect(handle, &QObject::destroyed, this, [this]() {
|
||||
this->mHyprlandHandle = nullptr;
|
||||
});
|
||||
}
|
||||
|
||||
emit this->hyprlandHandleChanged();
|
||||
}
|
||||
|
||||
void HyprlandToplevel::onActivatedChanged() {
|
||||
if (this->bUrgent.value()) {
|
||||
// If was urgent, and now active, clear urgent state
|
||||
this->bUrgent = false;
|
||||
}
|
||||
}
|
||||
|
||||
HyprlandToplevel* HyprlandToplevel::qmlAttachedProperties(QObject* object) {
|
||||
if (auto* toplevel = qobject_cast<Toplevel*>(object)) {
|
||||
return new HyprlandToplevel(toplevel);
|
||||
auto* ipc = HyprlandIpc::instance();
|
||||
return new HyprlandToplevel(ipc, toplevel);
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -2,49 +2,108 @@
|
|||
|
||||
#include <qcontainerfwd.h>
|
||||
#include <qobject.h>
|
||||
#include <qproperty.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
#include "../../toplevel_management/handle.hpp"
|
||||
#include "../../toplevel_management/qml.hpp"
|
||||
#include "connection.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
//! Exposes Hyprland window address for a Toplevel
|
||||
/// Attached object of @@Quickshell.Wayland.Toplevel which exposes
|
||||
/// a Hyprland window address for the window.
|
||||
//! Hyprland Toplevel
|
||||
/// Represents a window as Hyprland exposes it.
|
||||
/// Can also be used as an attached object of a @@Quickshell.Wayland.Toplevel,
|
||||
/// to resolve a handle to an Hyprland toplevel.
|
||||
class HyprlandToplevel: public QObject {
|
||||
Q_OBJECT;
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("");
|
||||
QML_ATTACHED(HyprlandToplevel);
|
||||
// clang-format off
|
||||
/// Hexadecimal Hyprland window address. Will be an empty string until
|
||||
/// the address is reported.
|
||||
Q_PROPERTY(QString address READ address NOTIFY addressChanged);
|
||||
Q_PROPERTY(QString address READ addressStr NOTIFY addressChanged);
|
||||
/// The toplevel handle, exposing the Hyprland toplevel.
|
||||
/// Will be null until the address is reported
|
||||
Q_PROPERTY(HyprlandToplevel* handle READ hyprlandHandle NOTIFY hyprlandHandleChanged);
|
||||
/// The wayland toplevel handle. Will be null intil the address is reported
|
||||
Q_PROPERTY(qs::wayland::toplevel_management::Toplevel* wayland READ waylandHandle NOTIFY waylandHandleChanged);
|
||||
/// The title of the toplevel
|
||||
Q_PROPERTY(QString title READ default NOTIFY titleChanged BINDABLE bindableTitle);
|
||||
/// Whether the toplevel is active or not
|
||||
Q_PROPERTY(bool activated READ default NOTIFY activatedChanged BINDABLE bindableActivated);
|
||||
/// Whether the client is urgent or not
|
||||
Q_PROPERTY(bool urgent READ default NOTIFY urgentChanged BINDABLE bindableUrgent);
|
||||
/// The current workspace of the toplevel (might be null)
|
||||
Q_PROPERTY(qs::hyprland::ipc::HyprlandWorkspace* workspace READ default NOTIFY workspaceChanged BINDABLE bindableWorkspace);
|
||||
/// The current monitor of the toplevel (might be null)
|
||||
Q_PROPERTY(qs::hyprland::ipc::HyprlandMonitor* monitor READ default NOTIFY monitorChanged BINDABLE bindableMonitor);
|
||||
// clang-format on
|
||||
|
||||
public:
|
||||
explicit HyprlandToplevel(qs::wayland::toplevel_management::Toplevel* toplevel);
|
||||
|
||||
[[nodiscard]] QString address() { return this->mAddress; }
|
||||
/// When invoked from HyprlandIpc, reacting to Hyprland's IPC events.
|
||||
explicit HyprlandToplevel(HyprlandIpc* ipc);
|
||||
/// When attached from a Toplevel
|
||||
explicit HyprlandToplevel(HyprlandIpc* ipc, qs::wayland::toplevel_management::Toplevel* toplevel);
|
||||
|
||||
static HyprlandToplevel* qmlAttachedProperties(QObject* object);
|
||||
|
||||
signals:
|
||||
void addressChanged();
|
||||
void updateInitial(quint64 address, const QString& title, const QString& workspaceName);
|
||||
|
||||
private slots:
|
||||
void onToplevelAddressed(
|
||||
qs::wayland::toplevel_management::impl::ToplevelHandle* handle,
|
||||
quint64 address
|
||||
);
|
||||
void updateFromObject(const QVariantMap& object);
|
||||
|
||||
private:
|
||||
[[nodiscard]] QString addressStr() const { return QString::number(this->mAddress, 16); }
|
||||
[[nodiscard]] quint64 address() const { return this->mAddress; }
|
||||
void setAddress(quint64 address);
|
||||
|
||||
QString mAddress;
|
||||
// doesn't have to be nulled on destroy, only used for comparison
|
||||
qs::wayland::toplevel_management::impl::ToplevelHandle* handle;
|
||||
// clang-format off
|
||||
[[nodiscard]] HyprlandToplevel* hyprlandHandle() { return this->mHyprlandHandle; }
|
||||
void setHyprlandHandle(HyprlandToplevel* handle);
|
||||
|
||||
[[nodiscard]] wayland::toplevel_management::Toplevel* waylandHandle();
|
||||
void setWaylandHandle(wayland::toplevel_management::impl::ToplevelHandle* handle);
|
||||
// clang-format on
|
||||
|
||||
[[nodiscard]] QBindable<QString> bindableTitle() { return &this->bTitle; }
|
||||
[[nodiscard]] QBindable<bool> bindableActivated() { return &this->bActivated; }
|
||||
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
|
||||
|
||||
[[nodiscard]] QBindable<HyprlandWorkspace*> bindableWorkspace() { return &this->bWorkspace; }
|
||||
void setWorkspace(HyprlandWorkspace* workspace);
|
||||
|
||||
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
|
||||
|
||||
signals:
|
||||
void addressChanged();
|
||||
QSDOC_HIDE void waylandHandleChanged();
|
||||
QSDOC_HIDE void hyprlandHandleChanged();
|
||||
|
||||
void titleChanged();
|
||||
void activatedChanged();
|
||||
void urgentChanged();
|
||||
void workspaceChanged();
|
||||
void monitorChanged();
|
||||
|
||||
private slots:
|
||||
void onActivatedChanged();
|
||||
|
||||
private:
|
||||
quint64 mAddress = 0;
|
||||
HyprlandIpc* ipc;
|
||||
|
||||
qs::wayland::toplevel_management::impl::ToplevelHandle* mWaylandHandle = nullptr;
|
||||
HyprlandToplevel* mHyprlandHandle = nullptr;
|
||||
|
||||
// clang-format off
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandToplevel, QString, bTitle, &HyprlandToplevel::titleChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandToplevel, bool, bActivated, &HyprlandToplevel::activatedChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandToplevel, bool, bUrgent, &HyprlandToplevel::urgentChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandToplevel, HyprlandWorkspace*, bWorkspace, &HyprlandToplevel::workspaceChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandToplevel, HyprlandMonitor*, bMonitor, &HyprlandToplevel::monitorChanged);
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
} // namespace qs::hyprland::ipc
|
||||
|
|
|
@ -28,6 +28,13 @@ HyprlandIpcQml::HyprlandIpcQml() {
|
|||
this,
|
||||
&HyprlandIpcQml::focusedMonitorChanged
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
instance,
|
||||
&HyprlandIpc::activeToplevelChanged,
|
||||
this,
|
||||
&HyprlandIpcQml::activeToplevelChanged
|
||||
);
|
||||
}
|
||||
|
||||
void HyprlandIpcQml::dispatch(const QString& request) {
|
||||
|
@ -51,6 +58,10 @@ QBindable<HyprlandWorkspace*> HyprlandIpcQml::bindableFocusedWorkspace() {
|
|||
return HyprlandIpc::instance()->bindableFocusedWorkspace();
|
||||
}
|
||||
|
||||
QBindable<HyprlandToplevel*> HyprlandIpcQml::bindableActiveToplevel() {
|
||||
return HyprlandIpc::instance()->bindableActiveToplevel();
|
||||
}
|
||||
|
||||
ObjectModel<HyprlandMonitor>* HyprlandIpcQml::monitors() {
|
||||
return HyprlandIpc::instance()->monitors();
|
||||
}
|
||||
|
@ -59,4 +70,8 @@ ObjectModel<HyprlandWorkspace>* HyprlandIpcQml::workspaces() {
|
|||
return HyprlandIpc::instance()->workspaces();
|
||||
}
|
||||
|
||||
ObjectModel<HyprlandToplevel>* HyprlandIpcQml::toplevels() {
|
||||
return HyprlandIpc::instance()->toplevels();
|
||||
}
|
||||
|
||||
} // namespace qs::hyprland::ipc
|
||||
|
|
|
@ -24,6 +24,8 @@ class HyprlandIpcQml: public QObject {
|
|||
Q_PROPERTY(qs::hyprland::ipc::HyprlandMonitor* focusedMonitor READ default NOTIFY focusedMonitorChanged BINDABLE bindableFocusedMonitor);
|
||||
/// The currently focused hyprland workspace. May be null.
|
||||
Q_PROPERTY(qs::hyprland::ipc::HyprlandWorkspace* focusedWorkspace READ default NOTIFY focusedWorkspaceChanged BINDABLE bindableFocusedWorkspace);
|
||||
/// Currently active toplevel (might be null)
|
||||
Q_PROPERTY(qs::hyprland::ipc::HyprlandToplevel* activeToplevel READ default NOTIFY activeToplevelChanged BINDABLE bindableActiveToplevel);
|
||||
/// All hyprland monitors.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::hyprland::ipc::HyprlandMonitor>*);
|
||||
Q_PROPERTY(UntypedObjectModel* monitors READ monitors CONSTANT);
|
||||
|
@ -32,6 +34,9 @@ class HyprlandIpcQml: public QObject {
|
|||
/// > [!NOTE] Named workspaces have a negative id, and will appear before unnamed workspaces.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::hyprland::ipc::HyprlandWorkspace>*);
|
||||
Q_PROPERTY(UntypedObjectModel* workspaces READ workspaces CONSTANT);
|
||||
/// All hyprland toplevels
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::hyprland::ipc::HyprlandToplevel>*);
|
||||
Q_PROPERTY(UntypedObjectModel* toplevels READ toplevels CONSTANT);
|
||||
// clang-format on
|
||||
QML_NAMED_ELEMENT(Hyprland);
|
||||
QML_SINGLETON;
|
||||
|
@ -61,8 +66,10 @@ public:
|
|||
[[nodiscard]] static QString eventSocketPath();
|
||||
[[nodiscard]] static QBindable<HyprlandMonitor*> bindableFocusedMonitor();
|
||||
[[nodiscard]] static QBindable<HyprlandWorkspace*> bindableFocusedWorkspace();
|
||||
[[nodiscard]] static QBindable<HyprlandToplevel*> bindableActiveToplevel();
|
||||
[[nodiscard]] static ObjectModel<HyprlandMonitor>* monitors();
|
||||
[[nodiscard]] static ObjectModel<HyprlandWorkspace>* workspaces();
|
||||
[[nodiscard]] static ObjectModel<HyprlandToplevel>* toplevels();
|
||||
|
||||
signals:
|
||||
/// Emitted for every event that comes in through the hyprland event socket (socket2).
|
||||
|
@ -72,6 +79,7 @@ signals:
|
|||
|
||||
void focusedMonitorChanged();
|
||||
void focusedWorkspaceChanged();
|
||||
void activeToplevelChanged();
|
||||
};
|
||||
|
||||
} // namespace qs::hyprland::ipc
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "workspace.hpp"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
#include <qcontainerfwd.h>
|
||||
|
@ -25,6 +26,12 @@ HyprlandWorkspace::HyprlandWorkspace(HyprlandIpc* ipc): QObject(ipc), ipc(ipc) {
|
|||
return this->ipc->bindableFocusedWorkspace().value() == this;
|
||||
});
|
||||
|
||||
QObject::connect(this, &HyprlandWorkspace::focusedChanged, this, [this]() {
|
||||
if (this->bFocused.value()) {
|
||||
this->updateUrgent();
|
||||
}
|
||||
});
|
||||
|
||||
Qt::endPropertyUpdateGroup();
|
||||
}
|
||||
|
||||
|
@ -82,6 +89,67 @@ void HyprlandWorkspace::setMonitor(HyprlandMonitor* monitor) {
|
|||
|
||||
void HyprlandWorkspace::onMonitorDestroyed() { this->bMonitor = nullptr; }
|
||||
|
||||
void HyprlandWorkspace::insertToplevel(HyprlandToplevel* toplevel) {
|
||||
if (!toplevel) return;
|
||||
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
|
||||
if (std::ranges::find(mList, toplevel) != mList.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->mToplevels.insertObject(toplevel);
|
||||
|
||||
QObject::connect(toplevel, &QObject::destroyed, this, [this, toplevel]() {
|
||||
this->removeToplevel(toplevel);
|
||||
});
|
||||
|
||||
QObject::connect(
|
||||
toplevel,
|
||||
&HyprlandToplevel::urgentChanged,
|
||||
this,
|
||||
&HyprlandWorkspace::updateUrgent
|
||||
);
|
||||
|
||||
this->updateUrgent();
|
||||
}
|
||||
|
||||
void HyprlandWorkspace::removeToplevel(HyprlandToplevel* toplevel) {
|
||||
if (!toplevel) return;
|
||||
|
||||
this->mToplevels.removeObject(toplevel);
|
||||
emit this->updateUrgent();
|
||||
QObject::disconnect(toplevel, nullptr, this, nullptr);
|
||||
}
|
||||
|
||||
// Triggered when there is an update either on the toplevel list, on a toplevel's urgent state
|
||||
void HyprlandWorkspace::updateUrgent() {
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
|
||||
const bool hasUrgentToplevel = std::ranges::any_of(mList, [&](HyprlandToplevel* toplevel) {
|
||||
return toplevel->bindableUrgent().value();
|
||||
});
|
||||
|
||||
if (this->bFocused && hasUrgentToplevel) {
|
||||
this->clearUrgent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasUrgentToplevel != this->bUrgent.value()) {
|
||||
this->bUrgent = hasUrgentToplevel;
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandWorkspace::clearUrgent() {
|
||||
this->bUrgent = false;
|
||||
|
||||
// Clear all urgent toplevels
|
||||
const auto& mList = this->mToplevels.valueList();
|
||||
for (auto* toplevel: mList) {
|
||||
toplevel->bindableUrgent().setValue(false);
|
||||
}
|
||||
}
|
||||
|
||||
void HyprlandWorkspace::activate() {
|
||||
this->ipc->dispatch(QString("workspace %1").arg(this->bId.value()));
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <qtypes.h>
|
||||
|
||||
#include "connection.hpp"
|
||||
#include "hyprland_toplevel.hpp"
|
||||
|
||||
namespace qs::hyprland::ipc {
|
||||
|
||||
|
@ -24,8 +25,11 @@ class HyprlandWorkspace: public QObject {
|
|||
/// 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);
|
||||
/// If this workspace has a window that is urgent.
|
||||
/// Becomes always falsed after the workspace is @@focused.
|
||||
Q_PROPERTY(bool urgent READ default NOTIFY urgentChanged BINDABLE bindableUrgent);
|
||||
/// If this workspace currently has a fullscreen client.
|
||||
Q_PROPERTY(bool hasFullscreen READ default NOTIFY focusedChanged BINDABLE bindableHasFullscreen);
|
||||
Q_PROPERTY(bool hasFullscreen READ default NOTIFY hasFullscreenChanged BINDABLE bindableHasFullscreen);
|
||||
/// Last json returned for this workspace, as a javascript object.
|
||||
///
|
||||
/// > [!WARNING] This is *not* updated unless the workspace object is fetched again from
|
||||
|
@ -33,6 +37,9 @@ class HyprlandWorkspace: public QObject {
|
|||
/// > property, run @@Hyprland.refreshWorkspaces() and wait for this property to update.
|
||||
Q_PROPERTY(QVariantMap lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged);
|
||||
Q_PROPERTY(qs::hyprland::ipc::HyprlandMonitor* monitor READ default NOTIFY monitorChanged BINDABLE bindableMonitor);
|
||||
/// List of toplevels on this workspace.
|
||||
QSDOC_TYPE_OVERRIDE(ObjectModel<qs::hyprland::ipc::HyprlandToplevel*);
|
||||
Q_PROPERTY(UntypedObjectModel* toplevels READ toplevels CONSTANT);
|
||||
// clang-format on
|
||||
QML_ELEMENT;
|
||||
QML_UNCREATABLE("HyprlandWorkspaces must be retrieved from the HyprlandIpc object.");
|
||||
|
@ -55,35 +62,46 @@ public:
|
|||
[[nodiscard]] QBindable<QString> bindableName() { return &this->bName; }
|
||||
[[nodiscard]] QBindable<bool> bindableActive() { return &this->bActive; }
|
||||
[[nodiscard]] QBindable<bool> bindableFocused() { return &this->bFocused; }
|
||||
[[nodiscard]] QBindable<bool> bindableUrgent() { return &this->bUrgent; }
|
||||
[[nodiscard]] QBindable<bool> bindableHasFullscreen() { return &this->bHasFullscreen; }
|
||||
[[nodiscard]] QBindable<HyprlandMonitor*> bindableMonitor() { return &this->bMonitor; }
|
||||
[[nodiscard]] ObjectModel<HyprlandToplevel>* toplevels() { return &this->mToplevels; }
|
||||
|
||||
[[nodiscard]] QVariantMap lastIpcObject() const;
|
||||
|
||||
void setMonitor(HyprlandMonitor* monitor);
|
||||
|
||||
void insertToplevel(HyprlandToplevel* toplevel);
|
||||
void removeToplevel(HyprlandToplevel* toplevel);
|
||||
|
||||
signals:
|
||||
void idChanged();
|
||||
void nameChanged();
|
||||
void activeChanged();
|
||||
void focusedChanged();
|
||||
void urgentChanged();
|
||||
void hasFullscreenChanged();
|
||||
void lastIpcObjectChanged();
|
||||
void monitorChanged();
|
||||
|
||||
private slots:
|
||||
void onMonitorDestroyed();
|
||||
void updateUrgent();
|
||||
|
||||
private:
|
||||
HyprlandIpc* ipc;
|
||||
void clearUrgent();
|
||||
|
||||
HyprlandIpc* ipc;
|
||||
QVariantMap mLastIpcObject;
|
||||
|
||||
ObjectModel<HyprlandToplevel> mToplevels {this};
|
||||
|
||||
// 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);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bUrgent, &HyprlandWorkspace::urgentChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, bool, bHasFullscreen, &HyprlandWorkspace::hasFullscreenChanged);
|
||||
Q_OBJECT_BINDABLE_PROPERTY(HyprlandWorkspace, HyprlandMonitor*, bMonitor, &HyprlandWorkspace::monitorChanged);
|
||||
// clang-format on
|
||||
|
|
37
src/wayland/hyprland/test/manual/toplevel-association.qml
Normal file
37
src/wayland/hyprland/test/manual/toplevel-association.qml
Normal file
|
@ -0,0 +1,37 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.Wayland
|
||||
|
||||
FloatingWindow {
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text { text: "Hyprland -> Wayland" }
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
model: Hyprland.toplevels
|
||||
delegate: Text {
|
||||
required property HyprlandToplevel modelData
|
||||
text: `${modelData} -> ${modelData.wayland}`
|
||||
}
|
||||
}
|
||||
|
||||
Text { text: "Wayland -> Hyprland" }
|
||||
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
clip: true
|
||||
model: ToplevelManager.toplevels
|
||||
delegate: Text {
|
||||
required property Toplevel modelData
|
||||
text: `${modelData} -> ${modelData.HyprlandToplevel.handle}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
src/wayland/hyprland/test/manual/toplevels.qml
Normal file
34
src/wayland/hyprland/test/manual/toplevels.qml
Normal file
|
@ -0,0 +1,34 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
|
||||
FloatingWindow {
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text { text: "Current toplevel:" }
|
||||
|
||||
ToplevelFromHyprland {
|
||||
modelData: Hyprland.activeToplevel
|
||||
}
|
||||
|
||||
Text { text: "\nAll toplevels:" }
|
||||
|
||||
ListView {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
clip: true
|
||||
model: Hyprland.toplevels
|
||||
delegate: ToplevelFromHyprland {}
|
||||
}
|
||||
}
|
||||
|
||||
component ToplevelFromHyprland: ColumnLayout {
|
||||
required property HyprlandToplevel modelData
|
||||
|
||||
Text {
|
||||
text: `Window 0x${modelData.address}, title: ${modelData.title}, activated: ${modelData.activated}, workspace id: ${modelData.workspace.id}, monitor name: ${modelData.monitor.name}, urgent: ${modelData.urgent}`
|
||||
}
|
||||
}
|
||||
}
|
34
src/wayland/hyprland/test/manual/workspaces.qml
Normal file
34
src/wayland/hyprland/test/manual/workspaces.qml
Normal file
|
@ -0,0 +1,34 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Widgets
|
||||
import Quickshell.Hyprland
|
||||
|
||||
FloatingWindow {
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: Hyprland.workspaces
|
||||
spacing: 5
|
||||
|
||||
delegate: WrapperRectangle {
|
||||
id: wsDelegate
|
||||
required property HyprlandWorkspace modelData
|
||||
color: "lightgray"
|
||||
|
||||
ColumnLayout {
|
||||
Text { text: `Workspace ${wsDelegate.modelData.id} on ${wsDelegate.modelData.monitor} | urgent: ${wsDelegate.modelData.urgent}`}
|
||||
|
||||
ColumnLayout {
|
||||
Repeater {
|
||||
model: wsDelegate.modelData.toplevels
|
||||
Text {
|
||||
id: tDelegate
|
||||
required property HyprlandToplevel modelData;
|
||||
text: `${tDelegate.modelData}: ${tDelegate.modelData.title}`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue