From 62f99f5754c6ca40b82b7e46e4a02cc075009f9d Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Sun, 3 Mar 2024 23:17:15 -0800 Subject: [PATCH] feat: add Process.manageLifetime --- src/core/plugin.cpp | 6 ++++++ src/core/plugin.hpp | 2 ++ src/core/rootwrapper.cpp | 10 +++++++--- src/io/CMakeLists.txt | 8 ++++++-- src/io/init.cpp | 12 ++++++++++++ src/io/plugin.cpp | 0 src/io/process.cpp | 29 +++++++++++++++++++++++++++++ src/io/process.hpp | 29 ++++++++++++++++++++++++++++- 8 files changed, 90 insertions(+), 6 deletions(-) create mode 100644 src/io/init.cpp create mode 100644 src/io/plugin.cpp diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 1485208..21312de 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -25,3 +25,9 @@ void QuickshellPlugin::initPlugins() { plugin->registerTypes(); } } + +void QuickshellPlugin::runOnReload() { + for (QuickshellPlugin* plugin: plugins) { + plugin->onReload(); + } +} diff --git a/src/core/plugin.hpp b/src/core/plugin.hpp index 92f1bc2..8a3719b 100644 --- a/src/core/plugin.hpp +++ b/src/core/plugin.hpp @@ -15,9 +15,11 @@ public: virtual bool applies() { return true; } virtual void init() {} virtual void registerTypes() {} + virtual void onReload() {} static void registerPlugin(QuickshellPlugin& plugin); static void initPlugins(); + static void runOnReload(); }; // NOLINTBEGIN diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 1ef7596..6fe9273 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -12,6 +12,7 @@ #include #include +#include "plugin.hpp" #include "qmlglobal.hpp" #include "reload.hpp" #include "shell.hpp" @@ -41,9 +42,8 @@ RootWrapper::~RootWrapper() { } void RootWrapper::reloadGraph(bool hard) { - QuickshellGlobal::deleteInstance(); - if (this->root != nullptr) { + QuickshellGlobal::deleteInstance(); this->engine.clearComponentCache(); } @@ -77,10 +77,14 @@ void RootWrapper::reloadGraph(bool hard) { oldRoot->deleteLater(); QTimer::singleShot(0, [this, newRoot]() { - if (this->root == newRoot) PostReloadHook::postReloadTree(this->root); + if (this->root == newRoot) { + QuickshellPlugin::runOnReload(); + PostReloadHook::postReloadTree(this->root); + } }); } else { PostReloadHook::postReloadTree(newRoot); + QuickshellPlugin::runOnReload(); } this->onConfigChanged(); diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt index c35c1ac..db69776 100644 --- a/src/io/CMakeLists.txt +++ b/src/io/CMakeLists.txt @@ -3,14 +3,18 @@ qt_add_library(quickshell-io STATIC process.cpp ) +add_library(quickshell-io-init OBJECT init.cpp) + if (SOCKETS) target_sources(quickshell-io PRIVATE socket.cpp) endif() qt_add_qml_module(quickshell-io URI Quickshell.Io) -target_link_libraries(quickshell-io PRIVATE ${QT_DEPS}) -target_link_libraries(quickshell PRIVATE quickshell-ioplugin) +target_link_libraries(quickshell-io PRIVATE ${QT_DEPS}) +target_link_libraries(quickshell-io-init PRIVATE ${QT_DEPS}) + +target_link_libraries(quickshell PRIVATE quickshell-ioplugin quickshell-io-init) if (TESTS) add_subdirectory(test) diff --git a/src/io/init.cpp b/src/io/init.cpp new file mode 100644 index 0000000..ea2bba7 --- /dev/null +++ b/src/io/init.cpp @@ -0,0 +1,12 @@ +#include "../core/plugin.hpp" +#include "process.hpp" + +namespace { + +class IoPlugin: public QuickshellPlugin { + void onReload() override { DisownedProcessContext::destroyInstance(); } +}; + +QS_REGISTER_PLUGIN(IoPlugin); + +} // namespace diff --git a/src/io/plugin.cpp b/src/io/plugin.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/io/process.cpp b/src/io/process.cpp index 7147b56..d6c5585 100644 --- a/src/io/process.cpp +++ b/src/io/process.cpp @@ -15,6 +15,10 @@ #include "../core/qmlglobal.hpp" #include "datastream.hpp" +// When the process ends this have no parent and is just leaked, +// meaning the destructor never runs and they are never killed. +static DisownedProcessContext* disownedCtx; // NOLINT + Process::Process(QObject* parent): QObject(parent) { QObject::connect( QuickshellGlobal::instance(), @@ -24,6 +28,13 @@ Process::Process(QObject* parent): QObject(parent) { ); } +Process::~Process() { + if (!this->mLifetimeManaged && this->process != nullptr) { + if (disownedCtx == nullptr) disownedCtx = new DisownedProcessContext(); // NOLINT + disownedCtx->reparent(this->process); + } +} + bool Process::isRunning() const { return this->process != nullptr; } void Process::setRunning(bool running) { @@ -161,6 +172,14 @@ void Process::setStdinEnabled(bool enabled) { emit this->stdinEnabledChanged(); } +bool Process::isLifetimeManaged() const { return this->mLifetimeManaged; } + +void Process::setLifetimeManaged(bool managed) { + if (managed == this->mLifetimeManaged) return; + this->mLifetimeManaged = managed; + emit this->lifetimeManagedChanged(); +} + void Process::startProcessIfReady() { if (this->process != nullptr || !this->targetRunning || this->mCommand.isEmpty()) return; this->targetRunning = false; @@ -261,3 +280,13 @@ void Process::write(const QString& data) { if (this->process == nullptr) return; this->process->write(data.toUtf8()); } + +void DisownedProcessContext::reparent(QProcess* process) { + process->setParent(this); + QObject::connect(process, &QProcess::finished, this, [process]() { process->deleteLater(); }); +} + +void DisownedProcessContext::destroyInstance() { + delete disownedCtx; + disownedCtx = nullptr; +} diff --git a/src/io/process.hpp b/src/io/process.hpp index 7d13f2c..004bbb6 100644 --- a/src/io/process.hpp +++ b/src/io/process.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -43,7 +44,8 @@ class Process: public QObject { Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged); /// The process ID of the running process or `null` if `running` is false. Q_PROPERTY(QVariant pid READ pid NOTIFY pidChanged); - /// The command to execute. + /// The command to execute. Each argument is its own string, which means you don't have + /// to deal with quoting anything. /// /// If the process is already running changing this property will affect the next /// started process. If the property has been changed after starting a process it will @@ -112,11 +114,21 @@ class Process: public QObject { /// If stdin is enabled. Defaults to true. If this property is set to false the process's stdin channel /// will be closed and [write](#func.write) will do nothing, even if set back to true. Q_PROPERTY(bool stdinEnabled READ stdinEnabled WRITE setStdinEnabled NOTIFY stdinEnabledChanged); + /// If the process should be killed when the Process object is destroyed or quickshell exits. + /// Defaults to true. + /// + /// This property may be changed while the process is running and will affect it. + /// + /// > [!WARNING] If set to false the process will still be killed if the quickshell config reloads. + /// > It will not be killed if quickshell exits normally or crashes. + Q_PROPERTY(bool manageLifetime READ isLifetimeManaged WRITE setLifetimeManaged NOTIFY lifetimeManagedChanged); // clang-format on QML_ELEMENT; public: explicit Process(QObject* parent = nullptr); + ~Process() override; + Q_DISABLE_COPY_MOVE(Process); /// Sends a signal to the process if `running` is true, otherwise does nothing. Q_INVOKABLE void signal(qint32 signal); @@ -150,6 +162,9 @@ public: [[nodiscard]] bool stdinEnabled() const; void setStdinEnabled(bool enabled); + [[nodiscard]] bool isLifetimeManaged() const; + void setLifetimeManaged(bool managed); + signals: void started(); void exited(qint32 exitCode, QProcess::ExitStatus exitStatus); @@ -163,6 +178,7 @@ signals: void stdoutParserChanged(); void stderrParserChanged(); void stdinEnabledChanged(); + void lifetimeManagedChanged(); private slots: void onStarted(); @@ -189,4 +205,15 @@ private: bool targetRunning = false; bool mStdinEnabled = false; bool mClearEnvironment = false; + bool mLifetimeManaged = true; +}; + +class DisownedProcessContext: public QObject { + Q_OBJECT; + + void reparent(QProcess* process); + friend class Process; + +public: + static void destroyInstance(); };