From f4d01d2d9f7a921f40a3b192637959ddf9129669 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Wed, 8 Jul 2026 22:51:44 +0200 Subject: ... --- .../shell/quickshell-plugins/CMakeLists.txt | 22 + .../shell/quickshell-plugins/ListUnitsEntry.cpp | 41 + .../shell/quickshell-plugins/ListUnitsEntry.hpp | 26 + .../shell/quickshell-plugins/Systemd.cpp | 92 +- .../shell/quickshell-plugins/Systemd.hpp | 46 + .../shell/quickshell-plugins/default.nix | 2 + .../org.freedesktop.systemd1.Manager.xml | 36 +- .../org.freedesktop.systemd1.Service.xml | 1100 ++++++++++++++++++++ .../org.freedesktop.systemd1.Unit.xml | 359 +++++++ accounts/gkleen@sif/shell/quickshell/Bar.qml | 14 +- .../shell/quickshell/Services/NiriService.qml | 1 + .../gkleen@sif/shell/quickshell/YtDlpWidget.qml | 190 ++++ accounts/gkleen@sif/systemd.nix | 43 +- 13 files changed, 1950 insertions(+), 22 deletions(-) create mode 100644 accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.cpp create mode 100644 accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.hpp create mode 100644 accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Service.xml create mode 100644 accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Unit.xml create mode 100644 accounts/gkleen@sif/shell/quickshell/YtDlpWidget.qml (limited to 'accounts') diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt index 020c0515..6b4f0d48 100644 --- a/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt +++ b/accounts/gkleen@sif/shell/quickshell-plugins/CMakeLists.txt @@ -114,6 +114,7 @@ qt_add_dbus_interface(DBUS_INTERFACES set_source_files_properties(org.freedesktop.systemd1.Manager.xml PROPERTIES CLASSNAME DBusSystemdManager NO_NAMESPACE TRUE + INCLUDE ListUnitsEntry.hpp ) qt_add_dbus_interface(DBUS_INTERFACES org.freedesktop.systemd1.Manager.xml @@ -138,6 +139,24 @@ qt_add_dbus_interface(DBUS_INTERFACES dbus_logind_session ) +set_source_files_properties(org.freedesktop.systemd1.Unit.xml PROPERTIES + CLASSNAME DBusSystemdUnit + NO_NAMESPACE TRUE +) +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.systemd1.Unit.xml + dbus_systemd_unit +) + +set_source_files_properties(org.freedesktop.systemd1.Service.xml PROPERTIES + CLASSNAME DBusSystemdService + NO_NAMESPACE TRUE +) +qt_add_dbus_interface(DBUS_INTERFACES + org.freedesktop.systemd1.Service.xml + dbus_systemd_service +) + set_source_files_properties(org.freedesktop.DBus.Properties.xml PROPERTIES CLASSNAME DBusProperties NO_NAMESPACE TRUE @@ -148,6 +167,7 @@ qt_add_dbus_interface(DBUS_INTERFACES ) include_directories(${CMAKE_SOURCE_DIR}/build) +include_directories(${QUICKSHELL_SRC}/src) target_compile_features(customplugin PUBLIC cxx_std_26) @@ -162,7 +182,9 @@ target_sources(customplugin PRIVATE FileSelector.cpp FileSelector.hpp KeePassXC.cpp KeePassXC.hpp Systemd.cpp Systemd.hpp + ListUnitsEntry.cpp ListUnitsEntry.hpp ${DBUS_INTERFACES} + ${QUICKSHELL_SRC}/src/core/model.cpp ${QUICKSHELL_SRC}/src/core/model.hpp ) install_qml_module(customplugin) diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.cpp new file mode 100644 index 00000000..c1f9537c --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.cpp @@ -0,0 +1,41 @@ +#include "ListUnitsEntry.hpp" + +void ListUnitsEntry::registerMetaType() { + qRegisterMetaType("ListUnitsEntry"); + qDBusRegisterMetaType(); +} + +QDBusArgument& operator<<(QDBusArgument& argument, const ListUnitsEntry& entry) { + argument.beginStructure(); + argument + << entry.primaryName + << entry.description + << entry.loadState + << entry.activeState + << entry.subState + << entry.followingUnit + << entry.unitPath + << entry.jobId + << entry.jobType + << entry.jobPath; + argument.endStructure(); + + return argument; +} +const QDBusArgument& operator>>(const QDBusArgument& argument, ListUnitsEntry& entry) { + argument.beginStructure(); + argument + >> entry.primaryName + >> entry.description + >> entry.loadState + >> entry.activeState + >> entry.subState + >> entry.followingUnit + >> entry.unitPath + >> entry.jobId + >> entry.jobType + >> entry.jobPath; + argument.endStructure(); + + return argument; +} diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.hpp new file mode 100644 index 00000000..15d789b9 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell-plugins/ListUnitsEntry.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +struct ListUnitsEntry { + QString primaryName; + QString description; + QString loadState; + QString activeState; + QString subState; + QString followingUnit; + QDBusObjectPath unitPath; + uint jobId; + QString jobType; + QDBusObjectPath jobPath; + + friend QDBusArgument& operator<<(QDBusArgument& argument, const ListUnitsEntry& entry); + friend const QDBusArgument& operator>>(const QDBusArgument& argument, ListUnitsEntry& entry); + static void registerMetaType(); +}; + +Q_DECLARE_METATYPE(ListUnitsEntry) + +using ListUnitsResult = QList; +Q_DECLARE_METATYPE(ListUnitsResult) diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp index 308659e9..6376b527 100644 --- a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp +++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.cpp @@ -8,7 +8,12 @@ #include #include +#include + Systemd::Systemd(QObject* parent) : QObject(parent) { + ListUnitsEntry::registerMetaType(); + qDBusRegisterMetaType(); + this->systemdManager = new DBusSystemdManager( "org.freedesktop.systemd1", "/org/freedesktop/systemd1", @@ -22,8 +27,9 @@ Systemd::Systemd(QObject* parent) : QObject(parent) { this ); + QDBusReply sessionPath = this->logindManager->GetSession("auto"); - qDebug() << sessionPath; + // qDebug() << sessionPath; this->logindSession = new DBusLogindSession( "org.freedesktop.login1", sessionPath.value().path(), @@ -42,6 +48,12 @@ Systemd::Systemd(QObject* parent) : QObject(parent) { QObject::connect(this->logindSession, &DBusLogindSession::Lock, this, &Systemd::lock); QObject::connect(this->logindSession, &DBusLogindSession::Unlock, this, &Systemd::unlock); QObject::connect(this->logindSessionProperties, &DBusProperties::PropertiesChanged, this, &Systemd::onLogindSessionPropertiesChanged); + this->systemdManager->Subscribe(); + QObject::connect(this->systemdManager, &DBusSystemdManager::UnitNew, this, &Systemd::onUserUnitNew); + QObject::connect(this->systemdManager, &DBusSystemdManager::UnitRemoved, this, &Systemd::onUserUnitRemoved); + + for (const auto& unit: this->systemdManager->ListUnits().value()) + this->onUserUnitNew(unit.primaryName, unit.unitPath); } void Systemd::onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties) { @@ -50,6 +62,82 @@ void Systemd::onLogindSessionPropertiesChanged(const QString& interface_name, co if (changed_properties.contains("LockedHint") || invalidated_properties.contains("LockedHint")) emit this->lockedHintChanged(); } +void Systemd::onUserUnitNew(const QString& id, const QDBusObjectPath& object_path) { + for (auto unit: this->mUserUnits.valueList()) + if (unit->path() == object_path) + return; + + for (const auto& unit: this->systemdManager->ListUnits().value()) + if (unit.unitPath == object_path) { + this->mUserUnits.insertObject(new SystemdUnit(this, object_path, id)); + return; + } +} +void Systemd::onUserUnitRemoved(const QString& id, const QDBusObjectPath& object_path) { + std::set toRemove; + for (auto unit: this->mUserUnits.valueList()) + if (unit->path() == object_path) + toRemove.insert(unit); + + for (auto unit: toRemove) { + this->mUserUnits.removeObject(unit); + delete unit; + } +} + +SystemdUnit::SystemdUnit(QObject* parent, const QDBusObjectPath& path, const QString& id): QObject(parent), mPath(path), mId(id) { + this->mUnitProperties = new DBusProperties( + "org.freedesktop.systemd1", + this->path().path(), + QDBusConnection::sessionBus(), + this + ); +} +std::unique_ptr SystemdUnit::systemdUnit() { + return std::make_unique( + "org.freedesktop.systemd1", + this->path().path(), + QDBusConnection::sessionBus(), + nullptr + ); +} +std::unique_ptr SystemdUnit::systemdService() { + if (!this->id().endsWith(".service")) + return std::unique_ptr(); + + return std::make_unique( + "org.freedesktop.systemd1", + this->path().path(), + QDBusConnection::sessionBus(), + nullptr + ); +} +QList SystemdUnit::runtimeDirectory() { + if (std::unique_ptr systemdService = this->systemdService()) { + if (!this->mRuntimeDirectory) + this->mRuntimeDirectory = systemdService->runtimeDirectory(); + return *this->mRuntimeDirectory; + } + + return QList{}; +} +QString SystemdUnit::activeState() { + if (!this->mActiveState) { + QObject::connect(this->mUnitProperties, &DBusProperties::PropertiesChanged, this, &SystemdUnit::onUnitPropertiesChanged); + this->mActiveState = this->systemdUnit()->activeState(); + } + + return *this->mActiveState; +} + +const QDBusObjectPath& SystemdUnit::path() const { return this->mPath; } +const QString& SystemdUnit::id() const { return this->mId; } +void SystemdUnit::onUnitPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties) { + if (changed_properties.contains("ActiveState") || invalidated_properties.contains("ActiveState")) { + this->mActiveState = this->systemdUnit()->activeState(); + emit this->activeStateChanged(); + } +} void Systemd::stopUserUnit(const QString& unit, const QString& mode) { this->systemdManager->StopUnit(unit, mode); } @@ -63,6 +151,8 @@ void Systemd::lockSession() { this->logindSession->call("Lock"); } void Systemd::suspend() { this->logindManager->Suspend(true); } void Systemd::hibernate() { this->logindManager->Hibernate(true); } +ObjectModel* Systemd::userUnits() { return &this->mUserUnits; } + std::string SystemdInhibitorParams::toString(SystemdInhibitorParams::WhatItem what) { if (what == SystemdInhibitorParams::Shutdown) return "shutdown"; diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp index 615024d2..98f73d8f 100644 --- a/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp +++ b/accounts/gkleen@sif/shell/quickshell-plugins/Systemd.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -11,8 +12,47 @@ #include "dbus_systemd_manager.h" #include "dbus_logind_manager.h" #include "dbus_logind_session.h" +#include "dbus_systemd_unit.h" +#include "dbus_systemd_service.h" #include "dbus_properties.h" +#include "core/model.hpp" + +class SystemdUnit : public QObject { + Q_OBJECT; + QML_ELEMENT; + QML_UNCREATABLE("SystemdUnits cannot be created directly"); + +public: + explicit SystemdUnit(QObject* parent, const QDBusObjectPath& path, const QString& id); + + Q_PROPERTY(QDBusObjectPath path READ path CONSTANT); + Q_PROPERTY(QString id READ id CONSTANT); + Q_PROPERTY(QList runtimeDirectory READ runtimeDirectory CONSTANT); + Q_PROPERTY(QString activeState READ activeState NOTIFY activeStateChanged); + + const QDBusObjectPath& path() const; + const QString& id() const; + QList runtimeDirectory(); + QString activeState(); + +signals: + void activeStateChanged(); + +private slots: + void onUnitPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties); + +private: + std::unique_ptr systemdUnit(); + std::unique_ptr systemdService(); + + DBusProperties* mUnitProperties; + QDBusObjectPath mPath; + QString mId; + std::optional mActiveState; + std::optional> mRuntimeDirectory; +}; + class Systemd : public QObject { Q_OBJECT; QML_SINGLETON; @@ -23,6 +63,7 @@ public: Q_PROPERTY(bool idleHint READ idleHint WRITE setIdleHint NOTIFY idleHintChanged) Q_PROPERTY(bool lockedHint READ lockedHint WRITE setLockedHint NOTIFY lockedHintChanged) + Q_PROPERTY(UntypedObjectModel* userUnits READ userUnits CONSTANT) Q_INVOKABLE void stopUserUnit(const QString& unit, const QString& mode); Q_INVOKABLE void setBrightness(const QString& subsystem, const QString& name, quint32 brightness); @@ -34,6 +75,7 @@ public: bool idleHint(); bool lockedHint(); + ObjectModel* userUnits(); signals: void shutdown(bool before); @@ -45,12 +87,16 @@ signals: private slots: void onLogindSessionPropertiesChanged(const QString& interface_name, const QVariantMap& changed_properties, const QStringList& invalidated_properties); + void onUserUnitNew(const QString& id, const QDBusObjectPath& objectPath); + void onUserUnitRemoved(const QString& id, const QDBusObjectPath& objectPath); private: DBusSystemdManager* systemdManager; DBusLogindManager* logindManager; DBusLogindSession* logindSession; DBusProperties* logindSessionProperties; + + ObjectModel mUserUnits{this}; }; class SystemdInhibitorParams : public QObject { diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix index 20a195eb..dfe93bca 100644 --- a/accounts/gkleen@sif/shell/quickshell-plugins/default.nix +++ b/accounts/gkleen@sif/shell/quickshell-plugins/default.nix @@ -5,6 +5,7 @@ , fmt , keepassxc , systemd +, quickshell }: stdenv.mkDerivation rec { @@ -24,6 +25,7 @@ stdenv.mkDerivation rec { cmakeFlags = [ (lib.cmakeFeature "INSTALL_QML_PREFIX" qt6.qtbase.qtQmlPrefix) + (lib.cmakeOptionType "string" "QUICKSHELL_SRC" (toString quickshell.src)) ]; LC_ALL = "C.UTF-8"; diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml index b4f84a13..1dadbc55 100644 --- a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml +++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Manager.xml @@ -562,22 +562,26 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Service.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Service.xml new file mode 100644 index 00000000..316dd080 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Service.xml @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Unit.xml b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Unit.xml new file mode 100644 index 00000000..a554f513 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell-plugins/org.freedesktop.systemd1.Unit.xml @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/accounts/gkleen@sif/shell/quickshell/Bar.qml b/accounts/gkleen@sif/shell/quickshell/Bar.qml index bce72077..33909884 100644 --- a/accounts/gkleen@sif/shell/quickshell/Bar.qml +++ b/accounts/gkleen@sif/shell/quickshell/Bar.qml @@ -89,7 +89,7 @@ PanelWindow { KeyboardLayout {} Item { - visible: privacy.visible + visible: privacy.visible || ytDlp.visible height: parent.height width: 8 - 4 } @@ -99,7 +99,17 @@ PanelWindow { } Item { - visible: privacy.visible + visible: privacy.visible && ytDlp.visible + height: parent.height + width: 8 - 4 + } + + YtDlpWidget { + id: ytDlp + } + + Item { + visible: privacy.visible || ytDlp.visible height: parent.height width: 8 - 4 } diff --git a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml index cd4ed125..e9e86f95 100644 --- a/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml +++ b/accounts/gkleen@sif/shell/quickshell/Services/NiriService.qml @@ -80,6 +80,7 @@ Singleton { eventCastStopped(event.CastStopped); else if (event.Ok && !eventStreamSocket.acked) { eventStreamSocket.acked = true; } else if (event.OverviewOpenedOrClosed) {} + else if (event.ScreenshotCaptured) {} else if (event.ConfigLoaded) {} else console.log(JSON.stringify(event)); diff --git a/accounts/gkleen@sif/shell/quickshell/YtDlpWidget.qml b/accounts/gkleen@sif/shell/quickshell/YtDlpWidget.qml new file mode 100644 index 00000000..ff338d49 --- /dev/null +++ b/accounts/gkleen@sif/shell/quickshell/YtDlpWidget.qml @@ -0,0 +1,190 @@ +import QtQml.Models +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls.Material +import Quickshell +import Quickshell.Io +import Quickshell.Widgets +import Custom as Custom + +Item { + id: root + + height: parent.height + width: icon.width + anchors.verticalCenter: parent.verticalCenter + + visible: Boolean(unitsModel.values.length) + + WrapperMouseArea { + id: widgetMouseArea + + anchors.fill: parent + hoverEnabled: true + + Item { + anchors.fill: parent + + MaterialDesignIcon { + id: icon + + implicitSize: 14 + anchors.centerIn: parent + + icon: "tray-arrow-down" + color: "white" + } + } + } + + PopupWindow { + id: tooltip + + property bool openPopup: false + property bool nextVisible: widgetMouseArea.containsMouse || tooltipMouseArea.containsMouse || openPopup + + anchor { + item: widgetMouseArea + edges: Edges.Bottom | Edges.Left + } + visible: false + + onNextVisibleChanged: hangTimer.restart() + + Timer { + id: hangTimer + interval: 100 + onTriggered: tooltip.visible = tooltip.nextVisible + } + + implicitWidth: tooltipContent.width + implicitHeight: tooltipContent.height + color: "transparent" + + Rectangle { + width: tooltip.width + height: tooltipLayout.childrenRect.height + 16 + color: "black" + } + + WrapperItem { + id: tooltipContent + + WrapperMouseArea { + id: tooltipMouseArea + + hoverEnabled: true + enabled: true + + WrapperItem { + margin: 8 + + child: GridLayout { + id: tooltipLayout + + columns: 2 + } + + Repeater { + model: ScriptModel { + id: unitsModel + + values: [...Custom.Systemd.userUnits.values].filter(unit => unit.id.match(/^yt-dlp@.+\.service$/) && unit.activeState === "active" && unit.runtimeDirectory) + } + + Item { + id: unitDelegate + + Component.onCompleted: { + while (children.length) { children[0].parent = tooltipLayout; } + } + + required property var modelData + required property int index + + property var state: null + + Socket { + id: socket + + path: Quickshell.env("XDG_RUNTIME_DIR") + "/" + unitDelegate.modelData.runtimeDirectory[0] + "/status.sock" + connected: true + onError: e => { + console.warn("YtDlpWidget: Error in Socket:", socket.path, e); + } + parser: SplitParser { + onRead: line => { + try { + unitDelegate.state = JSON.parse(line) + } catch (e) { + console.warn("YtDlpWidget: Failed to parse state:", line, e) + } + } + } + } + + WrapperItem { + Layout.column: 0 + Layout.row: unitDelegate.index + + implicitWidth: descText.contentWidth + implicitHeight: descText.contentHeight + + Text { + id: descText + + color: "white" + font.pointSize: 10 + font.family: "Fira Sans" + + text: unitDelegate.state?.title || unitDelegate.modelData.id + } + } + + /* + WrapperItem { + Layout.column: 1 + Layout.row: unitDelegate.index + + implicitWidth: fragText.contentWidth + implicitHeight: fragText.contentHeight + + visible: Boolean(unitDelegate.state) + + Text { + id: fragText + + color: "white" + font.pointSize: 10 + font.family: "Fira Sans" + + text: `${unitDelegate.state?.fragment_index}/${unitDelegate.state?.fragment_count}` + } + } + */ + + WrapperItem { + Layout.column: 1 + Layout.row: unitDelegate.index + + visible: Boolean(unitDelegate.state) + + ProgressBar { + id: progress + + Material.background: "black" + Material.accent: "white" + + from: 0 + to: unitDelegate.state?.total_bytes || unitDelegate.state?.total_bytes_estimate || 0 + value: unitDelegate.state?.downloaded_bytes || 0 + indeterminate: !Boolean(unitDelegate.state?.total_bytes || unitDelegate.state?.total_bytes_estimate) || unitDelegate.state?.status !== "downloading" + } + } + } + } + } + } + } + } +} diff --git a/accounts/gkleen@sif/systemd.nix b/accounts/gkleen@sif/systemd.nix index 2ccbaea0..955fe9f5 100644 --- a/accounts/gkleen@sif/systemd.nix +++ b/accounts/gkleen@sif/systemd.nix @@ -306,6 +306,7 @@ in { Service = { Type = "notify"; CacheDirectory = "yt-dlp/%N"; + RuntimeDirectory = "yt-dlp/%N"; StandardInput = "socket"; StandardOutput = "journal"; WatchdogSec = "30min"; @@ -315,6 +316,7 @@ in { ]; ExecStart = ''${lib.getExe pkgs.dscp} ${pkgs.writers.writePython3 "yt-dlp" { libraries = with pkgs.python3Packages; [ yt-dlp sdnotify ]; + flakeIgnore = [ "E501" ]; } '' import json from yt_dlp import YoutubeDL, parse_options @@ -322,14 +324,46 @@ in { from sdnotify import SystemdNotifier from os import environ from pathlib import Path + import threading + import socketserver - n = SystemdNotifier() + status = None + status_notifier = threading.Condition() + + + class StatusRequestHandler(socketserver.BaseRequestHandler): + def handle(self): + with self.request.makefile('w', encoding='utf-8') as fh: + while True: + with status_notifier: + json.dump(status, fh) + fh.write('\n') + fh.flush() + status_notifier.wait() + + + class ThreadedUnixStreamServer(socketserver.ThreadingMixIn, socketserver.UnixStreamServer): + pass + + + def progress_hook(d): + global status + n.notify('WATCHDOG=1\nSTATUS=' + d['status']) + with status_notifier: + status = { + **{k: v for k, v in d.items() if k in {'status', 'downloaded_bytes', 'total_bytes', 'total_bytes_estimate', 'fragment_count', 'fragment_index'}}, + **{k: v for k, v in (d['info_dict'] if 'info_dict' in d else {}).items() if k in {'title', 'filename'}}, + } + status_notifier.notify_all() + + + n = SystemdNotifier(debug=True) args = json.load(stdin) ydl_opts = { **parse_options().ydl_opts, - 'progress_hooks': [lambda _d: n.notify('WATCHDOG=1')], - 'postprocessor_hooks': [lambda _d: n.notify('WATCHDOG=1')], + 'progress_hooks': [progress_hook], + 'postprocessor_hooks': [progress_hook], 'progress_with_newline': True, 'progress_delta': 5, 'paths': { @@ -339,6 +373,9 @@ in { 'noplaylist': True, **(args['params'] if 'params' in args else {}), } + status_server = ThreadedUnixStreamServer(str(Path(environ['RUNTIME_DIRECTORY']) / 'status.sock'), StatusRequestHandler) + status_server.daemon_threads = True + threading.Thread(target=status_server.serve_forever, daemon=True).start() with YoutubeDL(ydl_opts) as ytdl: n.notify('READY=1') ytdl.download(args['urls']) -- cgit v1.2.3