feat: add Process.manageLifetime

This commit is contained in:
outfoxxed 2024-03-03 23:17:15 -08:00
parent 4cfe6ee0a1
commit 62f99f5754
Signed by untrusted user: outfoxxed
GPG key ID: 4C88A185FB89301E
8 changed files with 90 additions and 6 deletions

View file

@ -25,3 +25,9 @@ void QuickshellPlugin::initPlugins() {
plugin->registerTypes(); plugin->registerTypes();
} }
} }
void QuickshellPlugin::runOnReload() {
for (QuickshellPlugin* plugin: plugins) {
plugin->onReload();
}
}

View file

@ -15,9 +15,11 @@ public:
virtual bool applies() { return true; } virtual bool applies() { return true; }
virtual void init() {} virtual void init() {}
virtual void registerTypes() {} virtual void registerTypes() {}
virtual void onReload() {}
static void registerPlugin(QuickshellPlugin& plugin); static void registerPlugin(QuickshellPlugin& plugin);
static void initPlugins(); static void initPlugins();
static void runOnReload();
}; };
// NOLINTBEGIN // NOLINTBEGIN

View file

@ -12,6 +12,7 @@
#include <qtimer.h> #include <qtimer.h>
#include <qurl.h> #include <qurl.h>
#include "plugin.hpp"
#include "qmlglobal.hpp" #include "qmlglobal.hpp"
#include "reload.hpp" #include "reload.hpp"
#include "shell.hpp" #include "shell.hpp"
@ -41,9 +42,8 @@ RootWrapper::~RootWrapper() {
} }
void RootWrapper::reloadGraph(bool hard) { void RootWrapper::reloadGraph(bool hard) {
QuickshellGlobal::deleteInstance();
if (this->root != nullptr) { if (this->root != nullptr) {
QuickshellGlobal::deleteInstance();
this->engine.clearComponentCache(); this->engine.clearComponentCache();
} }
@ -77,10 +77,14 @@ void RootWrapper::reloadGraph(bool hard) {
oldRoot->deleteLater(); oldRoot->deleteLater();
QTimer::singleShot(0, [this, newRoot]() { QTimer::singleShot(0, [this, newRoot]() {
if (this->root == newRoot) PostReloadHook::postReloadTree(this->root); if (this->root == newRoot) {
QuickshellPlugin::runOnReload();
PostReloadHook::postReloadTree(this->root);
}
}); });
} else { } else {
PostReloadHook::postReloadTree(newRoot); PostReloadHook::postReloadTree(newRoot);
QuickshellPlugin::runOnReload();
} }
this->onConfigChanged(); this->onConfigChanged();

View file

@ -3,14 +3,18 @@ qt_add_library(quickshell-io STATIC
process.cpp process.cpp
) )
add_library(quickshell-io-init OBJECT init.cpp)
if (SOCKETS) if (SOCKETS)
target_sources(quickshell-io PRIVATE socket.cpp) target_sources(quickshell-io PRIVATE socket.cpp)
endif() endif()
qt_add_qml_module(quickshell-io URI Quickshell.Io) 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) if (TESTS)
add_subdirectory(test) add_subdirectory(test)

12
src/io/init.cpp Normal file
View file

@ -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

0
src/io/plugin.cpp Normal file
View file

View file

@ -15,6 +15,10 @@
#include "../core/qmlglobal.hpp" #include "../core/qmlglobal.hpp"
#include "datastream.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) { Process::Process(QObject* parent): QObject(parent) {
QObject::connect( QObject::connect(
QuickshellGlobal::instance(), 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; } bool Process::isRunning() const { return this->process != nullptr; }
void Process::setRunning(bool running) { void Process::setRunning(bool running) {
@ -161,6 +172,14 @@ void Process::setStdinEnabled(bool enabled) {
emit this->stdinEnabledChanged(); 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() { void Process::startProcessIfReady() {
if (this->process != nullptr || !this->targetRunning || this->mCommand.isEmpty()) return; if (this->process != nullptr || !this->targetRunning || this->mCommand.isEmpty()) return;
this->targetRunning = false; this->targetRunning = false;
@ -261,3 +280,13 @@ void Process::write(const QString& data) {
if (this->process == nullptr) return; if (this->process == nullptr) return;
this->process->write(data.toUtf8()); 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;
}

View file

@ -4,6 +4,7 @@
#include <qobject.h> #include <qobject.h>
#include <qprocess.h> #include <qprocess.h>
#include <qqmlintegration.h> #include <qqmlintegration.h>
#include <qtclasshelpermacros.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include <qvariant.h> #include <qvariant.h>
@ -43,7 +44,8 @@ class Process: public QObject {
Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged); Q_PROPERTY(bool running READ isRunning WRITE setRunning NOTIFY runningChanged);
/// The process ID of the running process or `null` if `running` is false. /// The process ID of the running process or `null` if `running` is false.
Q_PROPERTY(QVariant pid READ pid NOTIFY pidChanged); 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 /// 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 /// 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 /// 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. /// 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); 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 // clang-format on
QML_ELEMENT; QML_ELEMENT;
public: public:
explicit Process(QObject* parent = nullptr); 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. /// Sends a signal to the process if `running` is true, otherwise does nothing.
Q_INVOKABLE void signal(qint32 signal); Q_INVOKABLE void signal(qint32 signal);
@ -150,6 +162,9 @@ public:
[[nodiscard]] bool stdinEnabled() const; [[nodiscard]] bool stdinEnabled() const;
void setStdinEnabled(bool enabled); void setStdinEnabled(bool enabled);
[[nodiscard]] bool isLifetimeManaged() const;
void setLifetimeManaged(bool managed);
signals: signals:
void started(); void started();
void exited(qint32 exitCode, QProcess::ExitStatus exitStatus); void exited(qint32 exitCode, QProcess::ExitStatus exitStatus);
@ -163,6 +178,7 @@ signals:
void stdoutParserChanged(); void stdoutParserChanged();
void stderrParserChanged(); void stderrParserChanged();
void stdinEnabledChanged(); void stdinEnabledChanged();
void lifetimeManagedChanged();
private slots: private slots:
void onStarted(); void onStarted();
@ -189,4 +205,15 @@ private:
bool targetRunning = false; bool targetRunning = false;
bool mStdinEnabled = false; bool mStdinEnabled = false;
bool mClearEnvironment = false; bool mClearEnvironment = false;
bool mLifetimeManaged = true;
};
class DisownedProcessContext: public QObject {
Q_OBJECT;
void reparent(QProcess* process);
friend class Process;
public:
static void destroyInstance();
}; };