hyprland lua support #91

Merged
zach merged 5 commits from hypr-plugin into main 2026-05-20 14:08:30 +02:00
4 changed files with 217 additions and 32 deletions
+26 -4
View File
@@ -80,10 +80,32 @@ Singleton {
} }
function reloadHyprRules(): void { function reloadHyprRules(): void {
const barStr = Hyprland.usingLua ? `eval 'hl.layer_rule({ match = { namespace = "ZShell-Bar" }, %1 = true, %2 = true })'` : "keyword layerrule %1 %2, match:namespace ZShell-Bar"; const blur = transparency.enabled ? 1 : 0;
const authStr = Hyprland.usingLua ? `eval 'hl.layer_rule({ match = { namespace = "ZShell-Auth" }, %1 = true, %2 = true })'` : "keyword layerrule %1 %2, match:namespace ZShell-Auth"; const alpha = transparency.base - 0.03;
Hypr.extras.batchMessage([barStr.arg("blur").arg(transparency.enabled ? 1 : 0), barStr.arg("ignore_alpha").arg(transparency.base - 0.03)]);
Hypr.extras.batchMessage([authStr.arg("blur").arg(transparency.enabled ? 1 : 0), authStr.arg("ignore_alpha").arg(transparency.base - 0.03)]); const rules = `
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Bar" },
ignore_alpha = ${alpha}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
blur = ${blur}
})
hl.layer_rule({
match = { namespace = "ZShell-Auth" },
ignore_alpha = ${alpha}
})
`;
Hypr.extras.message(`eval ${rules}`);
} }
function setMode(mode: string): void { function setMode(mode: string): void {
+6 -6
View File
@@ -182,7 +182,7 @@ Variants {
property real extraHeight: 0.2 property real extraHeight: 0.2
deformAmount: 0.08 deformAmount: 0.06
implicitHeight: panels.dashboard.height * (1 + extraHeight) implicitHeight: panels.dashboard.height * (1 + extraHeight)
implicitWidth: panels.dashboard.width implicitWidth: panels.dashboard.width
panel: panels.dashboardWrapper panel: panels.dashboardWrapper
@@ -196,7 +196,7 @@ Variants {
property real extraHeight: 0.2 property real extraHeight: 0.2
deformAmount: 0.08 deformAmount: 0.06
implicitHeight: panels.launcher.height * (1 + extraHeight) implicitHeight: panels.launcher.height * (1 + extraHeight)
panel: panels.launcher panel: panels.launcher
radius: Appearance.rounding.smallest + 5 radius: Appearance.rounding.smallest + 5
@@ -207,7 +207,7 @@ Variants {
id: sidebarBg id: sidebarBg
bottomLeftRadius: 0 bottomLeftRadius: 0
deformAmount: 0.08 deformAmount: 0.04
exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg] exclude: panels.sidebar.offsetScale > 0.08 ? [] : [utilsBg]
implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2 implicitHeight: panel.height * (1 / rawDeformMatrix.m22) + 2
panel: panels.sidebar panel: panels.sidebar
@@ -262,7 +262,7 @@ Variants {
PanelBg { PanelBg {
id: resourcesBg id: resourcesBg
deformAmount: 0.08 deformAmount: 0.05
implicitHeight: panels.resources.height implicitHeight: panels.resources.height
implicitWidth: panels.resources.width implicitWidth: panels.resources.width
panel: panels.resourcesWrapper panel: panels.resourcesWrapper
@@ -276,10 +276,10 @@ Variants {
property real extraHeight: 0.2 property real extraHeight: 0.2
deformAmount: 0.08 deformAmount: 0.03
implicitHeight: panels.settings.height * (1 + extraHeight) implicitHeight: panels.settings.height * (1 + extraHeight)
implicitWidth: panels.settings.width implicitWidth: panels.settings.width
panel: panels.settingsWrapper panel: panels.settings
radius: Appearance.rounding.large radius: Appearance.rounding.large
topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller topLeftRadius: Appearance.rounding.large + Appearance.padding.smaller
topRightRadius: Appearance.rounding.large + Appearance.padding.smaller topRightRadius: Appearance.rounding.large + Appearance.padding.smaller
+5 -5
View File
@@ -13,11 +13,11 @@ Singleton {
function setHyprConf(): void { function setHyprConf(): void {
Hypr.extras.applyOptions({ Hypr.extras.applyOptions({
"animations:enabled": 0, "animations.enabled": 0,
"decoration:shadow:enabled": 0, "decoration.shadow.enabled": 0,
"decoration:blur:enabled": 0, "decoration.blur.enabled": 0,
"general:border_size": 0, "general.border_size": 0,
"decoration:rounding": 0 "decoration.rounding": 0
}); });
} }
+180 -17
View File
@@ -5,12 +5,166 @@
#include <qjsonarray.h> #include <qjsonarray.h>
#include <qlocalsocket.h> #include <qlocalsocket.h>
#include <qloggingcategory.h> #include <qloggingcategory.h>
#include <qmetatype.h>
#include <qregularexpression.h>
#include <qvariant.h> #include <qvariant.h>
Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg) Q_LOGGING_CATEGORY(lcHypr, "ZShell.internal.hypr", QtInfoMsg)
namespace ZShell::internal::hypr { namespace ZShell::internal::hypr {
namespace {
static QString luaEscapeString(const QString& s) {
QString out;
out.reserve(s.size() + 2);
out += QLatin1Char('"');
for (const QChar c : s) {
switch (c.unicode()) {
case '\\':
out += QLatin1String(R"(\\)");
break;
case '"':
out += QLatin1String(R"(\")");
break;
case '\n':
out += QLatin1String(R"(\n)");
break;
case '\r':
out += QLatin1String(R"(\r)");
break;
case '\t':
out += QLatin1String(R"(\t)");
break;
default:
out += c;
break;
}
}
out += QLatin1Char('"');
return out;
}
static QString luaValue(const QVariant& v);
static QString luaArray(const QVariantList& list) {
QStringList parts;
parts.reserve(list.size());
for (const auto& item : list) {
parts << luaValue(item);
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaArray(const QStringList& list) {
QStringList parts;
parts.reserve(list.size());
for (const auto& item : list) {
parts << luaEscapeString(item);
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaMapFromHash(const QVariantHash& hash) {
QStringList parts;
parts.reserve(hash.size());
for (auto it = hash.cbegin(); it != hash.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaMap(const QVariantMap& map) {
QStringList parts;
parts.reserve(map.size());
for (auto it = map.cbegin(); it != map.cend(); ++it) {
parts << luaEscapeString(it.key()) + QLatin1String(" = ") + luaValue(it.value());
}
return QLatin1String("{ ") + parts.join(QLatin1String(", ")) + QLatin1String(" }");
}
static QString luaValue(const QVariant& v) {
if (!v.isValid() || v.isNull()) {
return QLatin1String("nil");
}
switch (v.metaType().id()) {
case QMetaType::Bool:
return v.toBool() ? QLatin1String("true") : QLatin1String("false");
case QMetaType::Int:
case QMetaType::UInt:
case QMetaType::LongLong:
case QMetaType::ULongLong:
case QMetaType::Double:
return v.toString();
case QMetaType::QString:
return luaEscapeString(v.toString());
case QMetaType::QStringList:
return luaArray(v.toStringList());
case QMetaType::QVariantList:
return luaArray(v.toList());
case QMetaType::QVariantMap:
return luaMap(v.toMap());
case QMetaType::QVariantHash:
return luaMapFromHash(v.toHash());
default:
return luaEscapeString(v.toString());
}
}
static QString normalizeOptionPath(QString key) {
key = key.trimmed();
key.replace(QLatin1Char(':'), QLatin1Char('.'));
return key;
}
static QString buildHlConfigCall(const QString& key, const QVariant& value) {
const auto parts = normalizeOptionPath(key).split(QLatin1Char('.'), Qt::SkipEmptyParts);
if (parts.isEmpty()) {
return {};
}
QString out;
out.reserve(32 + key.size() + value.toString().size());
out += QLatin1String("hl.config({ ");
for (int i = 0; i < parts.size(); ++i) {
out += parts.at(i);
out += QLatin1String(" = ");
if (i + 1 < parts.size()) {
out += QLatin1String("{ ");
}
}
out += luaValue(value);
for (int i = 0; i + 1 < parts.size(); ++i) {
out += QLatin1String(" }");
}
out += QLatin1String(" })");
return out;
}
} // namespace
HyprExtras::HyprExtras(QObject* parent) HyprExtras::HyprExtras(QObject* parent)
: QObject(parent) : QObject(parent)
, m_requestSocket("") , m_requestSocket("")
@@ -26,7 +180,7 @@ HyprExtras::HyprExtras(QObject* parent)
auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his); auto hyprDir = QString("%1/hypr/%2").arg(qEnvironmentVariable("XDG_RUNTIME_DIR"), his);
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
hyprDir = "/tmp/hypr/" + his; hyprDir = QStringLiteral("/tmp/hypr/") + his;
if (!QDir(hyprDir).exists()) { if (!QDir(hyprDir).exists()) {
qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket."; qCWarning(lcHypr) << "Hyprland socket directory does not exist. Unable to connect to Hyprland socket.";
@@ -34,8 +188,8 @@ HyprExtras::HyprExtras(QObject* parent)
} }
} }
m_requestSocket = hyprDir + "/.socket.sock"; m_requestSocket = hyprDir + QStringLiteral("/.socket.sock");
m_eventSocket = hyprDir + "/.socket2.sock"; m_eventSocket = hyprDir + QStringLiteral("/.socket2.sock");
refreshOptions(); refreshOptions();
refreshDevices(); refreshDevices();
@@ -74,7 +228,8 @@ void HyprExtras::batchMessage(const QStringList& messages) {
return; return;
} }
makeRequest("[[BATCH]]" + messages.join(";"), [](bool success, const QByteArray& res) { makeRequest(QStringLiteral("[[BATCH]]") + messages.join(QLatin1Char(';')),
[](bool success, const QByteArray& res) {
if (!success) { if (!success) {
qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res); qCWarning(lcHypr) << "batchMessage: request error:" << QString::fromUtf8(res);
} }
@@ -86,14 +241,21 @@ void HyprExtras::applyOptions(const QVariantHash& options) {
return; return;
} }
QString request; QStringList calls;
request.reserve(12 + options.size() * 40); calls.reserve(options.size());
request += QLatin1String("[[BATCH]]");
for (auto it = options.constBegin(); it != options.constEnd(); ++it) { for (auto it = options.constBegin(); it != options.constEnd(); ++it) {
request += QLatin1String("keyword ") + it.key() + QLatin1Char(' ') + it.value().toString() + QLatin1Char(';'); const auto call = buildHlConfigCall(it.key(), it.value());
if (!call.isEmpty()) {
calls << call;
}
} }
makeRequest(request, [this](bool success, const QByteArray& res) { if (calls.isEmpty()) {
return;
}
makeRequest(QStringLiteral("eval ") + calls.join(QLatin1String("; ")), [this](bool success, const QByteArray& res) {
if (success) { if (success) {
refreshOptions(); refreshOptions();
} else { } else {
@@ -107,7 +269,7 @@ void HyprExtras::refreshOptions() {
m_optionsRefresh->close(); m_optionsRefresh->close();
} }
m_optionsRefresh = makeRequestJson("descriptions", [this](bool success, const QJsonDocument& response) { m_optionsRefresh = makeRequestJson(QStringLiteral("descriptions"), [this](bool success, const QJsonDocument& response) {
m_optionsRefresh.reset(); m_optionsRefresh.reset();
if (!success) { if (!success) {
return; return;
@@ -118,8 +280,9 @@ void HyprExtras::refreshOptions() {
for (const auto& o : std::as_const(options)) { for (const auto& o : std::as_const(options)) {
const auto obj = o.toObject(); const auto obj = o.toObject();
const auto key = obj.value("value").toString(); const auto key = obj.value(QStringLiteral("value")).toString();
const auto value = obj.value("data").toObject().value("current").toVariant(); const auto value = obj.value(QStringLiteral("data")).toObject().value(QStringLiteral("current")).toVariant();
if (m_options.value(key) != value) { if (m_options.value(key) != value) {
dirty = true; dirty = true;
m_options.insert(key, value); m_options.insert(key, value);
@@ -137,7 +300,7 @@ void HyprExtras::refreshDevices() {
m_devicesRefresh->close(); m_devicesRefresh->close();
} }
m_devicesRefresh = makeRequestJson("devices", [this](bool success, const QJsonDocument& response) { m_devicesRefresh = makeRequestJson(QStringLiteral("devices"), [this](bool success, const QJsonDocument& response) {
m_devicesRefresh.reset(); m_devicesRefresh.reset();
if (success) { if (success) {
m_devices->updateLastIpcObject(response.object()); m_devices->updateLastIpcObject(response.object());
@@ -167,23 +330,23 @@ void HyprExtras::readEvent() {
if (rawEvent.isEmpty()) { if (rawEvent.isEmpty()) {
break; break;
} }
rawEvent.truncate(rawEvent.length() - 1); // Remove trailing \n rawEvent.truncate(rawEvent.length() - 1);
const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>")); const auto event = QByteArrayView(rawEvent.data(), rawEvent.indexOf(">>"));
handleEvent(QString::fromUtf8(event)); handleEvent(QString::fromUtf8(event));
} }
} }
void HyprExtras::handleEvent(const QString& event) { void HyprExtras::handleEvent(const QString& event) {
if (event == "configreloaded") { if (event == QStringLiteral("configreloaded")) {
refreshOptions(); refreshOptions();
} else if (event == "activelayout") { } else if (event == QStringLiteral("activelayout")) {
refreshDevices(); refreshDevices();
} }
} }
HyprExtras::SocketPtr HyprExtras::makeRequestJson( HyprExtras::SocketPtr HyprExtras::makeRequestJson(
const QString& request, const std::function<void(bool, QJsonDocument)>& callback) { const QString& request, const std::function<void(bool, QJsonDocument)>& callback) {
return makeRequest("j/" + request, [callback](bool success, const QByteArray& response) { return makeRequest(QStringLiteral("j/") + request, [callback](bool success, const QByteArray& response) {
callback(success, QJsonDocument::fromJson(response)); callback(success, QJsonDocument::fromJson(response));
}); });
} }