update hypr toplevels

This commit is contained in:
Zacharias-Brohn
2025-11-18 19:25:45 +01:00
parent 5b069bf4c2
commit a40299ed41
13 changed files with 712 additions and 46 deletions
+130
View File
@@ -0,0 +1,130 @@
pragma Singleton
import ZShell
import ZShell.Internal
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import QtQuick
Singleton {
id: root
readonly property var toplevels: Hyprland.toplevels
readonly property var workspaces: Hyprland.workspaces
readonly property var monitors: Hyprland.monitors
readonly property HyprlandToplevel activeToplevel: Hyprland.activeToplevel?.wayland?.activated ? Hyprland.activeToplevel : null
readonly property HyprlandWorkspace focusedWorkspace: Hyprland.focusedWorkspace
readonly property HyprlandMonitor focusedMonitor: Hyprland.focusedMonitor
readonly property int activeWsId: focusedWorkspace?.id ?? 1
readonly property HyprKeyboard keyboard: extras.devices.keyboards.find(kb => kb.main) ?? null
readonly property bool capsLock: keyboard?.capsLock ?? false
readonly property bool numLock: keyboard?.numLock ?? false
readonly property string defaultKbLayout: keyboard?.layout.split(",")[0] ?? "??"
readonly property string kbLayoutFull: keyboard?.activeKeymap ?? "Unknown"
readonly property string kbLayout: kbMap.get(kbLayoutFull) ?? "??"
readonly property var kbMap: new Map()
readonly property alias extras: extras
readonly property alias options: extras.options
readonly property alias devices: extras.devices
property bool hadKeyboard
signal configReloaded
function dispatch(request: string): void {
Hyprland.dispatch(request);
}
function monitorFor(screen: ShellScreen): HyprlandMonitor {
return Hyprland.monitorFor(screen);
}
function reloadDynamicConfs(): void {
extras.batchMessage(["keyword bindlni ,Caps_Lock,global,caelestia:refreshDevices", "keyword bindlni ,Num_Lock,global,caelestia:refreshDevices"]);
}
Component.onCompleted: reloadDynamicConfs()
Connections {
target: Hyprland
function onRawEvent(event: HyprlandEvent): void {
const n = event.name;
if (n.endsWith("v2"))
return;
if (n === "configreloaded") {
root.configReloaded();
root.reloadDynamicConfs();
} else if (["workspace", "moveworkspace", "activespecial", "focusedmon"].includes(n)) {
Hyprland.refreshWorkspaces();
Hyprland.refreshMonitors();
} else if (["openwindow", "closewindow", "movewindow"].includes(n)) {
Hyprland.refreshToplevels();
Hyprland.refreshWorkspaces();
} else if (n.includes("mon")) {
Hyprland.refreshMonitors();
} else if (n.includes("workspace")) {
Hyprland.refreshWorkspaces();
} else if (n.includes("window") || n.includes("group") || ["pin", "fullscreen", "changefloatingmode", "minimize"].includes(n)) {
Hyprland.refreshToplevels();
}
}
}
FileView {
id: kbLayoutFile
path: Quickshell.env("CAELESTIA_XKB_RULES_PATH") || "/usr/share/X11/xkb/rules/base.lst"
onLoaded: {
const layoutMatch = text().match(/! layout\n([\s\S]*?)\n\n/);
if (layoutMatch) {
const lines = layoutMatch[1].split("\n");
for (const line of lines) {
if (!line.trim() || line.trim().startsWith("!"))
continue;
const match = line.match(/^\s*([a-z]{2,})\s+([a-zA-Z() ]+)$/);
if (match)
root.kbMap.set(match[2], match[1]);
}
}
const variantMatch = text().match(/! variant\n([\s\S]*?)\n\n/);
if (variantMatch) {
const lines = variantMatch[1].split("\n");
for (const line of lines) {
if (!line.trim() || line.trim().startsWith("!"))
continue;
const match = line.match(/^\s*([a-zA-Z0-9_-]+)\s+([a-z]{2,}): (.+)$/);
if (match)
root.kbMap.set(match[3], match[2]);
}
}
}
}
IpcHandler {
target: "hypr"
function refreshDevices(): void {
extras.refreshDevices();
}
}
GlobalShortcut {
name: "refreshDevices"
appid: "ZShell"
onPressed: extras.refreshDevices()
onReleased: extras.refreshDevices()
}
HyprExtras {
id: extras
}
}
+6 -5
View File
@@ -3,11 +3,11 @@ pragma ComponentBehavior: Bound
import ZShell import ZShell
import Quickshell import Quickshell
import Quickshell.Wayland import Quickshell.Wayland
import Quickshell.Hyprland
import QtQuick import QtQuick
import QtQuick.Effects import QtQuick.Effects
import qs.Modules import qs.Modules
import qs.Config import qs.Config
import qs.Helpers
MouseArea { MouseArea {
id: root id: root
@@ -17,8 +17,8 @@ MouseArea {
property bool onClient property bool onClient
property real realRounding: 6 property real realBorderWidth: onClient ? (Hypr.options["general:border_size"] ?? 1) : 2
property real realBorderWidth: 1 property real realRounding: onClient ? (Hypr.options["decoration:rounding"] ?? 0) : 0
property real ssx property real ssx
property real ssy property real ssy
@@ -34,14 +34,14 @@ MouseArea {
property real sh: Math.abs(sy - ey) property real sh: Math.abs(sy - ey)
property list<var> clients: { property list<var> clients: {
const mon = Hyprland.monitorFor(screen); const mon = Hypr.monitorFor(screen);
if (!mon) if (!mon)
return []; return [];
const special = mon.lastIpcObject.specialWorkspace; const special = mon.lastIpcObject.specialWorkspace;
const wsId = special.name ? special.id : mon.activeWorkspace.id; const wsId = special.name ? special.id : mon.activeWorkspace.id;
return Hyprland.toplevels.values.filter(c => c.workspace?.id === wsId).sort((a, b) => { return Hypr.toplevels.values.filter(c => c.workspace?.id === wsId).sort((a, b) => {
const ac = a.lastIpcObject; const ac = a.lastIpcObject;
const bc = b.lastIpcObject; const bc = b.lastIpcObject;
return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating); return (bc.pinned - ac.pinned) || ((bc.fullscreen !== 0) - (ac.fullscreen !== 0)) || (bc.floating - ac.floating);
@@ -84,6 +84,7 @@ MouseArea {
cursorShape: Qt.CrossCursor cursorShape: Qt.CrossCursor
Component.onCompleted: { Component.onCompleted: {
Hypr.extras.refreshOptions();
if (loader.freeze) if (loader.freeze)
clients = clients; clients = clients;
+2 -2
View File
@@ -103,8 +103,8 @@ Repeater {
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
Layout.fillHeight: true Layout.fillHeight: true
Layout.preferredWidth: 30 Layout.preferredWidth: 30
color: collapseArea.containsMouse ? "#15FFFFFF" : "transparent" color: !groupColumn.isExpanded ? "#E53935" : collapseArea.containsMouse ? "#15FFFFFF" : "transparent"
radius: groupColumn.isExpanded ? 4 : Layout.preferredHeight / 2 radius: groupColumn.isExpanded ? 4 : height / 2
visible: true visible: true
Text { Text {
View File
+31
View File
@@ -131,6 +131,20 @@ PanelWindow {
border.color: "#555555" border.color: "#555555"
radius: 8 radius: 8
// Rectangle {
// anchors.bottom: parent.bottom
// anchors.left: parent.left
// anchors.right: parent.right
// height: 4
// bottomLeftRadius: parent.radius
// bottomRightRadius: parent.radius
// color: "#40000000"
// Rectangle {
// anchors.fill: parent
// width: parent.width * ( Math.max(0, Math.min( rootItem.modelData.timer ) ) )
// }
// }
Component.onCompleted: { Component.onCompleted: {
root.notifRegions.push( notifRegion.createObject(root, { item: backgroundRect })); root.notifRegions.push( notifRegion.createObject(root, { item: backgroundRect }));
} }
@@ -217,6 +231,23 @@ PanelWindow {
} }
} }
} }
ElapsedTimer {
id: timer
}
}
MouseArea {
property int timePassed
anchors.fill: parent
hoverEnabled: true
onEntered: {
// rootItem.modelData.timer.interval = 5000 - timer.restartMs();
rootItem.modelData.timer.stop();
}
onExited: {
rootItem.modelData.timer.start();
}
} }
} }
} }
+2 -11
View File
@@ -1,5 +1,3 @@
//@ pragma Env QT_STYLE_OVERRIDE=Breeze
import QtQuick import QtQuick
import Quickshell import Quickshell
import Quickshell.Services.SystemTray import Quickshell.Services.SystemTray
@@ -50,12 +48,6 @@ MouseArea {
Connections { Connections {
target: trayMenu target: trayMenu
function onVisibleChanged() {
if ( !trayMenu.visible ) {
trayMenu.trayMenu = null;
}
}
function onFinishedLoading() { function onFinishedLoading() {
if ( !root.hasLoaded ) if ( !root.hasLoaded )
trayMenu.visible = false; trayMenu.visible = false;
@@ -67,9 +59,8 @@ MouseArea {
if ( mouse.button === Qt.LeftButton ) { if ( mouse.button === Qt.LeftButton ) {
root.item.activate(); root.item.activate();
} else if ( mouse.button === Qt.RightButton ) { } else if ( mouse.button === Qt.RightButton ) {
if ( root.item?.menu !== trayMenu.trayMenu ) { trayMenu.trayMenu = null;
trayMenu.trayMenu = root.item?.menu; trayMenu.trayMenu = root.item?.menu;
}
trayMenu.visible = !trayMenu.visible; trayMenu.visible = !trayMenu.visible;
trayMenu.focusGrab = true; trayMenu.focusGrab = true;
} }
+53 -28
View File
@@ -7,6 +7,7 @@ import QtQuick.Layouts
import Qt5Compat.GraphicalEffects import Qt5Compat.GraphicalEffects
import Quickshell.Hyprland import Quickshell.Hyprland
import QtQml import QtQml
import qs.Effects
PanelWindow { PanelWindow {
id: root id: root
@@ -58,26 +59,11 @@ PanelWindow {
} }
onVisibleChanged: { onVisibleChanged: {
if ( visible ) { if ( !visible )
scaleValue = 0;
scaleAnimation.start();
} else {
root.menuStack.pop(); root.menuStack.pop();
backEntry.visible = false; backEntry.visible = false;
}
}
NumberAnimation { openAnim.start();
id: scaleAnimation
target: root
property: "scaleValue"
from: 0
to: 1
duration: 150
easing.type: Easing.OutCubic
onStopped: {
root.updateMask();
}
} }
HyprlandFocusGrab { HyprlandFocusGrab {
@@ -85,7 +71,7 @@ PanelWindow {
windows: [ root ] windows: [ root ]
active: false active: false
onCleared: { onCleared: {
root.visible = false; closeAnim.start();
} }
} }
@@ -143,6 +129,54 @@ PanelWindow {
} }
} }
ParallelAnimation {
id: closeAnim
Anim {
target: menuRect
property: "implicitHeight"
to: 0
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Anim {
targets: [ menuRect, shadowRect ]
property: "opacity"
from: 1
to: 0
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
onFinished: {
root.visible = false;
}
}
ParallelAnimation {
id: openAnim
Anim {
target: menuRect
property: "implicitHeight"
from: 0
to: listLayout.contentHeight + ( root.menuStack.length > 0 ? root.entryHeight + 10 : 10 )
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
Anim {
targets: [ menuRect, shadowRect ]
property: "opacity"
from: 0
to: 1
duration: MaterialEasing.expressiveEffectsTime
easing.bezierCurve: MaterialEasing.expressiveEffects
}
}
ShadowRect {
id: shadowRect
anchors.fill: menuRect
radius: menuRect.radius
}
Rectangle { Rectangle {
id: menuRect id: menuRect
x: Math.round( root.trayItemRect.x - ( menuRect.implicitWidth / 2 ) + 11 ) x: Math.round( root.trayItemRect.x - ( menuRect.implicitWidth / 2 ) + 11 )
@@ -154,15 +188,6 @@ PanelWindow {
border.color: "#40FFFFFF" border.color: "#40FFFFFF"
clip: true clip: true
transform: [
Scale {
origin.x: menuRect.width / 2
origin.y: 0
xScale: root.scaleValue
yScale: root.scaleValue
}
]
Behavior on implicitWidth { Behavior on implicitWidth {
NumberAnimation { NumberAnimation {
duration: MaterialEasing.expressiveEffectsTime duration: MaterialEasing.expressiveEffectsTime
@@ -242,7 +267,7 @@ PanelWindow {
if ( !menuItem.modelData.hasChildren ) { if ( !menuItem.modelData.hasChildren ) {
if ( menuItem.modelData.enabled ) { if ( menuItem.modelData.enabled ) {
menuItem.modelData.triggered(); menuItem.modelData.triggered();
root.visible = false; closeAnim.start();
} }
} else { } else {
root.menuStack.push(root.trayMenu); root.menuStack.push(root.trayMenu);
+1
View File
@@ -41,3 +41,4 @@ qml_module(ZShell
) )
add_subdirectory(Models) add_subdirectory(Models)
add_subdirectory(Internal)
+6
View File
@@ -0,0 +1,6 @@
qml_module(ZShell-internal
URI ZShell.Internal
SOURCES
hyprextras.hpp hyprextras.cpp
hyprdevices.hpp hyprdevices.cpp
)
+134
View File
@@ -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
+74
View File
@@ -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
+217
View File
@@ -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
+56
View File
@@ -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