update hypr toplevels
This commit is contained in:
@@ -41,3 +41,4 @@ qml_module(ZShell
|
||||
)
|
||||
|
||||
add_subdirectory(Models)
|
||||
add_subdirectory(Internal)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
qml_module(ZShell-internal
|
||||
URI ZShell.Internal
|
||||
SOURCES
|
||||
hyprextras.hpp hyprextras.cpp
|
||||
hyprdevices.hpp hyprdevices.cpp
|
||||
)
|
||||
@@ -0,0 +1,134 @@
|
||||
#include "hyprdevices.hpp"
|
||||
|
||||
#include <qjsonarray.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
HyprKeyboard::HyprKeyboard(QJsonObject ipcObject, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_lastIpcObject(ipcObject) {}
|
||||
|
||||
QVariantHash HyprKeyboard::lastIpcObject() const {
|
||||
return m_lastIpcObject.toVariantHash();
|
||||
}
|
||||
|
||||
QString HyprKeyboard::address() const {
|
||||
return m_lastIpcObject.value("address").toString();
|
||||
}
|
||||
|
||||
QString HyprKeyboard::name() const {
|
||||
return m_lastIpcObject.value("name").toString();
|
||||
}
|
||||
|
||||
QString HyprKeyboard::layout() const {
|
||||
return m_lastIpcObject.value("layout").toString();
|
||||
}
|
||||
|
||||
QString HyprKeyboard::activeKeymap() const {
|
||||
return m_lastIpcObject.value("active_keymap").toString();
|
||||
}
|
||||
|
||||
bool HyprKeyboard::capsLock() const {
|
||||
return m_lastIpcObject.value("capsLock").toBool();
|
||||
}
|
||||
|
||||
bool HyprKeyboard::numLock() const {
|
||||
return m_lastIpcObject.value("numLock").toBool();
|
||||
}
|
||||
|
||||
bool HyprKeyboard::main() const {
|
||||
return m_lastIpcObject.value("main").toBool();
|
||||
}
|
||||
|
||||
bool HyprKeyboard::updateLastIpcObject(QJsonObject object) {
|
||||
if (m_lastIpcObject == object) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto last = m_lastIpcObject;
|
||||
|
||||
m_lastIpcObject = object;
|
||||
emit lastIpcObjectChanged();
|
||||
|
||||
bool dirty = false;
|
||||
if (last.value("address") != object.value("address")) {
|
||||
dirty = true;
|
||||
emit addressChanged();
|
||||
}
|
||||
if (last.value("name") != object.value("name")) {
|
||||
dirty = true;
|
||||
emit nameChanged();
|
||||
}
|
||||
if (last.value("layout") != object.value("layout")) {
|
||||
dirty = true;
|
||||
emit layoutChanged();
|
||||
}
|
||||
if (last.value("active_keymap") != object.value("active_keymap")) {
|
||||
dirty = true;
|
||||
emit activeKeymapChanged();
|
||||
}
|
||||
if (last.value("capsLock") != object.value("capsLock")) {
|
||||
dirty = true;
|
||||
emit capsLockChanged();
|
||||
}
|
||||
if (last.value("numLock") != object.value("numLock")) {
|
||||
dirty = true;
|
||||
emit numLockChanged();
|
||||
}
|
||||
if (last.value("main") != object.value("main")) {
|
||||
dirty = true;
|
||||
emit mainChanged();
|
||||
}
|
||||
return dirty;
|
||||
}
|
||||
|
||||
HyprDevices::HyprDevices(QObject* parent)
|
||||
: QObject(parent) {}
|
||||
|
||||
QQmlListProperty<HyprKeyboard> HyprDevices::keyboards() {
|
||||
return QQmlListProperty<HyprKeyboard>(this, &m_keyboards);
|
||||
}
|
||||
|
||||
bool HyprDevices::updateLastIpcObject(QJsonObject object) {
|
||||
const auto val = object.value("keyboards").toArray();
|
||||
bool dirty = false;
|
||||
|
||||
for (auto it = m_keyboards.begin(); it != m_keyboards.end();) {
|
||||
auto* const keyboard = *it;
|
||||
const auto inNewValues = std::any_of(val.begin(), val.end(), [keyboard](const QJsonValue& o) {
|
||||
return o.toObject().value("address").toString() == keyboard->address();
|
||||
});
|
||||
|
||||
if (!inNewValues) {
|
||||
dirty = true;
|
||||
it = m_keyboards.erase(it);
|
||||
keyboard->deleteLater();
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& o : val) {
|
||||
const auto obj = o.toObject();
|
||||
const auto addr = obj.value("address").toString();
|
||||
|
||||
auto it = std::find_if(m_keyboards.begin(), m_keyboards.end(), [addr](const HyprKeyboard* kb) {
|
||||
return kb->address() == addr;
|
||||
});
|
||||
|
||||
if (it != m_keyboards.end()) {
|
||||
dirty |= (*it)->updateLastIpcObject(obj);
|
||||
} else {
|
||||
dirty = true;
|
||||
m_keyboards << new HyprKeyboard(obj, this);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
emit keyboardsChanged();
|
||||
}
|
||||
|
||||
return dirty;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <qjsonobject.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qqmllist.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
class HyprKeyboard : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("HyprKeyboard instances can only be retrieved from a HyprDevices")
|
||||
|
||||
Q_PROPERTY(QVariantHash lastIpcObject READ lastIpcObject NOTIFY lastIpcObjectChanged)
|
||||
Q_PROPERTY(QString address READ address NOTIFY addressChanged)
|
||||
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
|
||||
Q_PROPERTY(QString layout READ layout NOTIFY layoutChanged)
|
||||
Q_PROPERTY(QString activeKeymap READ activeKeymap NOTIFY activeKeymapChanged)
|
||||
Q_PROPERTY(bool capsLock READ capsLock NOTIFY capsLockChanged)
|
||||
Q_PROPERTY(bool numLock READ numLock NOTIFY numLockChanged)
|
||||
Q_PROPERTY(bool main READ main NOTIFY mainChanged)
|
||||
|
||||
public:
|
||||
explicit HyprKeyboard(QJsonObject ipcObject, QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QVariantHash lastIpcObject() const;
|
||||
[[nodiscard]] QString address() const;
|
||||
[[nodiscard]] QString name() const;
|
||||
[[nodiscard]] QString layout() const;
|
||||
[[nodiscard]] QString activeKeymap() const;
|
||||
[[nodiscard]] bool capsLock() const;
|
||||
[[nodiscard]] bool numLock() const;
|
||||
[[nodiscard]] bool main() const;
|
||||
|
||||
bool updateLastIpcObject(QJsonObject object);
|
||||
|
||||
signals:
|
||||
void lastIpcObjectChanged();
|
||||
void addressChanged();
|
||||
void nameChanged();
|
||||
void layoutChanged();
|
||||
void activeKeymapChanged();
|
||||
void capsLockChanged();
|
||||
void numLockChanged();
|
||||
void mainChanged();
|
||||
|
||||
private:
|
||||
QJsonObject m_lastIpcObject;
|
||||
};
|
||||
|
||||
class HyprDevices : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("HyprDevices instances can only be retrieved from a HyprExtras")
|
||||
|
||||
Q_PROPERTY(
|
||||
QQmlListProperty<ZShell::internal::hypr::HyprKeyboard> keyboards READ keyboards NOTIFY keyboardsChanged)
|
||||
|
||||
public:
|
||||
explicit HyprDevices(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QQmlListProperty<HyprKeyboard> keyboards();
|
||||
|
||||
bool updateLastIpcObject(QJsonObject object);
|
||||
|
||||
signals:
|
||||
void keyboardsChanged();
|
||||
|
||||
private:
|
||||
QList<HyprKeyboard*> m_keyboards;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
@@ -0,0 +1,217 @@
|
||||
#include "hyprextras.hpp"
|
||||
|
||||
#include <qdir.h>
|
||||
#include <qjsonarray.h>
|
||||
#include <qlocalsocket.h>
|
||||
#include <qvariant.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
HyprExtras::HyprExtras(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_requestSocket("")
|
||||
, m_eventSocket("")
|
||||
, m_socket(nullptr)
|
||||
, m_socketValid(false)
|
||||
, m_devices(new HyprDevices(this)) {
|
||||
const auto his = qEnvironmentVariable("HYPRLAND_INSTANCE_SIGNATURE");
|
||||
if (his.isEmpty()) {
|
||||
qWarning()
|
||||
<< "HyprExtras::HyprExtras: $HYPRLAND_INSTANCE_SIGNATURE is unset. Unable to connect to Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
hyprDir = "/tmp/hypr/" + his;
|
||||
|
||||
if (!QDir(hyprDir).exists()) {
|
||||
qWarning() << "HyprExtras::HyprExtras: Hyprland socket directory does not exist. Unable to connect to "
|
||||
"Hyprland socket.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_requestSocket = hyprDir + "/.socket.sock";
|
||||
m_eventSocket = hyprDir + "/.socket2.sock";
|
||||
|
||||
refreshOptions();
|
||||
refreshDevices();
|
||||
|
||||
m_socket = new QLocalSocket(this);
|
||||
|
||||
QObject::connect(m_socket, &QLocalSocket::errorOccurred, this, &HyprExtras::socketError);
|
||||
QObject::connect(m_socket, &QLocalSocket::stateChanged, this, &HyprExtras::socketStateChanged);
|
||||
QObject::connect(m_socket, &QLocalSocket::readyRead, this, &HyprExtras::readEvent);
|
||||
|
||||
m_socket->connectToServer(m_eventSocket, QLocalSocket::ReadOnly);
|
||||
}
|
||||
|
||||
QVariantHash HyprExtras::options() const {
|
||||
return m_options;
|
||||
}
|
||||
|
||||
HyprDevices* HyprExtras::devices() const {
|
||||
return m_devices;
|
||||
}
|
||||
|
||||
void HyprExtras::message(const QString& message) {
|
||||
if (message.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest(message, [](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qWarning() << "HyprExtras::message: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::batchMessage(const QStringList& messages) {
|
||||
if (messages.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) {
|
||||
if (!success) {
|
||||
qWarning() << "HyprExtras::batchMessage: request error:" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::applyOptions(const QVariantHash& options) {
|
||||
if (options.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString request = "[[BATCH]]";
|
||||
for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
|
||||
request += QString("keyword %1 %2;").arg(it.key(), it.value().toString());
|
||||
}
|
||||
|
||||
makeRequest(request, [this](bool success, const QByteArray& res) {
|
||||
if (success) {
|
||||
refreshOptions();
|
||||
} else {
|
||||
qWarning() << "HyprExtras::applyOptions: request error" << QString::fromUtf8(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::refreshOptions() {
|
||||
if (!m_optionsRefresh.isNull()) {
|
||||
m_optionsRefresh->close();
|
||||
}
|
||||
|
||||
m_optionsRefresh = makeRequestJson("descriptions", [this](bool success, const QJsonDocument& response) {
|
||||
m_optionsRefresh.reset();
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto options = response.array();
|
||||
bool dirty = false;
|
||||
|
||||
for (const auto& o : std::as_const(options)) {
|
||||
const auto obj = o.toObject();
|
||||
const auto key = obj.value("value").toString();
|
||||
const auto value = obj.value("data").toObject().value("current").toVariant();
|
||||
if (m_options.value(key) != value) {
|
||||
dirty = true;
|
||||
m_options.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
if (dirty) {
|
||||
emit optionsChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::refreshDevices() {
|
||||
if (!m_devicesRefresh.isNull()) {
|
||||
m_devicesRefresh->close();
|
||||
}
|
||||
|
||||
m_devicesRefresh = makeRequestJson("devices", [this](bool success, const QJsonDocument& response) {
|
||||
m_devicesRefresh.reset();
|
||||
if (success) {
|
||||
m_devices->updateLastIpcObject(response.object());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void HyprExtras::socketError(QLocalSocket::LocalSocketError error) const {
|
||||
if (!m_socketValid) {
|
||||
qWarning() << "HyprExtras::socketError: unable to connect to Hyprland event socket:" << error;
|
||||
} else {
|
||||
qWarning() << "HyprExtras::socketError: Hyprland event socket error:" << error;
|
||||
}
|
||||
}
|
||||
|
||||
void HyprExtras::socketStateChanged(QLocalSocket::LocalSocketState state) {
|
||||
if (state == QLocalSocket::UnconnectedState && m_socketValid) {
|
||||
qWarning() << "HyprExtras::socketStateChanged: Hyprland event socket disconnected.";
|
||||
}
|
||||
|
||||
m_socketValid = state == QLocalSocket::ConnectedState;
|
||||
}
|
||||
|
||||
void HyprExtras::readEvent() {
|
||||
while (true) {
|
||||
auto rawEvent = m_socket->readLine();
|
||||
if (rawEvent.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n
|
||||
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
|
||||
handleEvent(QString::fromUtf8(event));
|
||||
}
|
||||
}
|
||||
|
||||
void HyprExtras::handleEvent(const QString& event) {
|
||||
if (event == "configreloaded") {
|
||||
refreshOptions();
|
||||
} else if (event == "activelayout") {
|
||||
refreshDevices();
|
||||
}
|
||||
}
|
||||
|
||||
HyprExtras::SocketPtr HyprExtras::makeRequestJson(
|
||||
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
|
||||
return makeRequest("j/" + request, [callback](bool success, const QByteArray& response) {
|
||||
callback(success, QJsonDocument::fromJson(response));
|
||||
});
|
||||
}
|
||||
|
||||
HyprExtras::SocketPtr HyprExtras::makeRequest(
|
||||
const QString& request, const std::function<void(bool, QByteArray)>& callback) {
|
||||
if (m_requestSocket.isEmpty()) {
|
||||
return SocketPtr();
|
||||
}
|
||||
|
||||
auto socket = SocketPtr::create(this);
|
||||
|
||||
QObject::connect(socket.data(), &QLocalSocket::connected, this, [=, this]() {
|
||||
QObject::connect(socket.data(), &QLocalSocket::readyRead, this, [socket, callback]() {
|
||||
const auto response = socket->readAll();
|
||||
callback(true, std::move(response));
|
||||
socket->close();
|
||||
});
|
||||
|
||||
socket->write(request.toUtf8());
|
||||
socket->flush();
|
||||
});
|
||||
|
||||
QObject::connect(socket.data(), &QLocalSocket::errorOccurred, this, [=](QLocalSocket::LocalSocketError err) {
|
||||
qWarning() << "HyprExtras::makeRequest: error making request:" << err << "| request:" << request;
|
||||
callback(false, {});
|
||||
socket->close();
|
||||
});
|
||||
|
||||
socket->connectToServer(m_requestSocket);
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "hyprdevices.hpp"
|
||||
#include <qlocalsocket.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
namespace ZShell::internal::hypr {
|
||||
|
||||
class HyprExtras : public QObject {
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(QVariantHash options READ options NOTIFY optionsChanged)
|
||||
Q_PROPERTY(ZShell::internal::hypr::HyprDevices* devices READ devices CONSTANT)
|
||||
|
||||
public:
|
||||
explicit HyprExtras(QObject* parent = nullptr);
|
||||
|
||||
[[nodiscard]] QVariantHash options() const;
|
||||
[[nodiscard]] HyprDevices* devices() const;
|
||||
|
||||
Q_INVOKABLE void message(const QString& message);
|
||||
Q_INVOKABLE void batchMessage(const QStringList& messages);
|
||||
Q_INVOKABLE void applyOptions(const QVariantHash& options);
|
||||
|
||||
Q_INVOKABLE void refreshOptions();
|
||||
Q_INVOKABLE void refreshDevices();
|
||||
|
||||
signals:
|
||||
void optionsChanged();
|
||||
|
||||
private:
|
||||
using SocketPtr = QSharedPointer<QLocalSocket>;
|
||||
|
||||
QString m_requestSocket;
|
||||
QString m_eventSocket;
|
||||
QLocalSocket* m_socket;
|
||||
bool m_socketValid;
|
||||
|
||||
QVariantHash m_options;
|
||||
HyprDevices* const m_devices;
|
||||
|
||||
SocketPtr m_optionsRefresh;
|
||||
SocketPtr m_devicesRefresh;
|
||||
|
||||
void socketError(QLocalSocket::LocalSocketError error) const;
|
||||
void socketStateChanged(QLocalSocket::LocalSocketState state);
|
||||
void readEvent();
|
||||
void handleEvent(const QString& event);
|
||||
|
||||
SocketPtr makeRequestJson(const QString& request, const std::function<void(bool, QJsonDocument)>& callback);
|
||||
SocketPtr makeRequest(const QString& request, const std::function<void(bool, QByteArray)>& callback);
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal::hypr
|
||||
Reference in New Issue
Block a user