diff --git a/src/core/qmlglobal.cpp b/src/core/qmlglobal.cpp index 4915546..fdcf0da 100644 --- a/src/core/qmlglobal.cpp +++ b/src/core/qmlglobal.cpp @@ -2,7 +2,9 @@ #include #include +#include #include +#include #include #include #include @@ -83,3 +85,29 @@ QVariant QuickshellGlobal::env(const QString& variable) { // NOLINT return qEnvironmentVariable(vstr.data()); } + +QString QuickshellGlobal::workingDirectory() const { // NOLINT + return QDir::current().absolutePath(); +} + +void QuickshellGlobal::setWorkingDirectory(const QString& workingDirectory) { // NOLINT + QDir::setCurrent(workingDirectory); + emit this->workingDirectoryChanged(); +} + +static QuickshellGlobal* g_instance = nullptr; // NOLINT + +QuickshellGlobal* QuickshellGlobal::create(QQmlEngine* /*unused*/, QJSEngine* /*unused*/) { + return QuickshellGlobal::instance(); +} + +QuickshellGlobal* QuickshellGlobal::instance() { + if (g_instance == nullptr) g_instance = new QuickshellGlobal(); + QJSEngine::setObjectOwnership(g_instance, QJSEngine::CppOwnership); + return g_instance; +} + +void QuickshellGlobal::deleteInstance() { + delete g_instance; + g_instance = nullptr; +} diff --git a/src/core/qmlglobal.hpp b/src/core/qmlglobal.hpp index f9b4ff8..ceb8673 100644 --- a/src/core/qmlglobal.hpp +++ b/src/core/qmlglobal.hpp @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -12,6 +14,7 @@ class QuickshellGlobal: public QObject { Q_OBJECT; + // clang-format off /// All currently connected screens. /// /// This property updates as connected screens change. @@ -33,6 +36,9 @@ class QuickshellGlobal: public QObject { /// This creates an instance of your window once on every screen. /// As screens are added or removed your window will be created or destroyed on those screens. Q_PROPERTY(QQmlListProperty screens READ screens NOTIFY screensChanged); + /// Quickshell's working directory. Defaults to whereever quickshell was launched from. + Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged); + // clang-format on QML_SINGLETON; QML_NAMED_ELEMENT(Quickshell); @@ -54,8 +60,16 @@ public: /// Returns the string value of an environment variable or null if it is not set. Q_INVOKABLE QVariant env(const QString& variable); + [[nodiscard]] QString workingDirectory() const; + void setWorkingDirectory(const QString& workingDirectory); + + static QuickshellGlobal* create(QQmlEngine* /*unused*/, QJSEngine* /*unused*/); + static QuickshellGlobal* instance(); + static void deleteInstance(); + signals: void screensChanged(); + void workingDirectoryChanged(); public slots: void updateScreens(); diff --git a/src/core/rootwrapper.cpp b/src/core/rootwrapper.cpp index 34ef81f..baf4e4c 100644 --- a/src/core/rootwrapper.cpp +++ b/src/core/rootwrapper.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -10,6 +11,7 @@ #include #include +#include "qmlglobal.hpp" #include "reload.hpp" #include "shell.hpp" #include "watcher.hpp" @@ -17,7 +19,8 @@ RootWrapper::RootWrapper(QString rootPath) : QObject(nullptr) , rootPath(std::move(rootPath)) - , engine(this) { + , engine(this) + , originalWorkingDirectory(QDir::current().absolutePath()) { this->reloadGraph(true); if (this->root == nullptr) { @@ -28,14 +31,19 @@ RootWrapper::RootWrapper(QString rootPath) RootWrapper::~RootWrapper() { // event loop may no longer be running so deleteLater is not an option + QuickshellGlobal::deleteInstance(); delete this->root; } void RootWrapper::reloadGraph(bool hard) { + QuickshellGlobal::deleteInstance(); + if (this->root != nullptr) { this->engine.clearComponentCache(); } + QDir::setCurrent(this->originalWorkingDirectory); + auto component = QQmlComponent(&this->engine, QUrl::fromLocalFile(this->rootPath)); auto* obj = component.beginCreate(this->engine.rootContext()); diff --git a/src/core/rootwrapper.hpp b/src/core/rootwrapper.hpp index 7e78a18..2a09c68 100644 --- a/src/core/rootwrapper.hpp +++ b/src/core/rootwrapper.hpp @@ -28,4 +28,5 @@ private: QQmlEngine engine; ShellRoot* root = nullptr; FiletreeWatcher* configWatcher = nullptr; + QString originalWorkingDirectory; }; diff --git a/src/core/shell.cpp b/src/core/shell.cpp index efa281e..59a678f 100644 --- a/src/core/shell.cpp +++ b/src/core/shell.cpp @@ -1,5 +1,6 @@ #include "shell.hpp" +#include #include void ShellRoot::setConfig(ShellConfig config) { @@ -9,3 +10,7 @@ void ShellRoot::setConfig(ShellConfig config) { } ShellConfig ShellRoot::config() const { return this->mConfig; } + +void ShellConfig::setWorkingDirectory(const QString& workingDirectory) { // NOLINT + QDir::setCurrent(workingDirectory); +} diff --git a/src/core/shell.hpp b/src/core/shell.hpp index 318be18..f5978df 100644 --- a/src/core/shell.hpp +++ b/src/core/shell.hpp @@ -10,9 +10,12 @@ class ShellConfig { Q_GADGET; Q_PROPERTY(bool watchFiles MEMBER mWatchFiles); + Q_PROPERTY(QString workingDirectory WRITE setWorkingDirectory); public: bool mWatchFiles = true; + + void setWorkingDirectory(const QString& workingDirectory); }; ///! Root config element @@ -20,6 +23,8 @@ class ShellRoot: public ReloadPropagator { Q_OBJECT; /// If `config.watchFiles` is true the configuration will be reloaded whenever it changes. /// Defaults to true. + /// + /// `config.workingDirectory` corrosponds to [Quickshell.workingDirectory](../quickshell#prop.workingDirectory). Q_PROPERTY(ShellConfig config READ config WRITE setConfig); QML_ELEMENT; diff --git a/src/io/process.cpp b/src/io/process.cpp index b971347..7a88fd5 100644 --- a/src/io/process.cpp +++ b/src/io/process.cpp @@ -2,6 +2,7 @@ #include // NOLINT #include +#include #include #include #include @@ -9,8 +10,18 @@ #include #include +#include "../core/qmlglobal.hpp" #include "datastream.hpp" +Process::Process(QObject* parent): QObject(parent) { + QObject::connect( + QuickshellGlobal::instance(), + &QuickshellGlobal::workingDirectoryChanged, + this, + &Process::onGlobalWorkingDirectoryChanged + ); +} + bool Process::isRunning() const { return this->process != nullptr; } void Process::setRunning(bool running) { @@ -24,6 +35,25 @@ QVariant Process::pid() const { return QVariant::fromValue(this->process->processId()); } +QString Process::workingDirectory() const { + if (this->mWorkingDirectory.isEmpty()) return QDir::current().absolutePath(); + else return this->mWorkingDirectory; +} + +void Process::setWorkingDirectory(const QString& workingDirectory) { + auto absolute = + workingDirectory.isEmpty() ? workingDirectory : QDir(workingDirectory).absolutePath(); + if (absolute == this->mWorkingDirectory) return; + this->mWorkingDirectory = absolute; + emit this->workingDirectoryChanged(); +} + +void Process::onGlobalWorkingDirectoryChanged() { + if (this->mWorkingDirectory.isEmpty()) { + emit this->workingDirectoryChanged(); + } +} + QList Process::command() const { return this->mCommand; } void Process::setCommand(QList command) { @@ -137,6 +167,8 @@ void Process::startProcessIfReady() { if (this->mStderrParser == nullptr) this->process->closeReadChannel(QProcess::StandardError); if (!this->mStdinEnabled) this->process->closeWriteChannel(); + if (!this->mWorkingDirectory.isEmpty()) + this->process->setWorkingDirectory(this->mWorkingDirectory); this->process->start(cmd, args); } diff --git a/src/io/process.hpp b/src/io/process.hpp index 95d0241..ab3f0ff 100644 --- a/src/io/process.hpp +++ b/src/io/process.hpp @@ -42,6 +42,14 @@ 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 working directory of the process. Defaults to [quickshell's working directory]. + /// + /// 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 + /// return the new value, not the one for the currently running process. + /// + /// [quickshell's working directory]: ../../quickshell/quickshell#prop.workingDirectory + Q_PROPERTY(QString workingDirectory READ workingDirectory WRITE setWorkingDirectory NOTIFY workingDirectoryChanged); /// The command to execute. /// /// If the process is already running changing this property will affect the next @@ -64,7 +72,7 @@ class Process: public QObject { QML_ELEMENT; public: - explicit Process(QObject* parent = nullptr): QObject(parent) {} + explicit Process(QObject* parent = nullptr); /// Sends a signal to the process if `running` is true, otherwise does nothing. Q_INVOKABLE void signal(qint32 signal); @@ -77,6 +85,9 @@ public: [[nodiscard]] QVariant pid() const; + [[nodiscard]] QString workingDirectory() const; + void setWorkingDirectory(const QString& workingDirectory); + [[nodiscard]] QList command() const; void setCommand(QList command); @@ -95,6 +106,7 @@ signals: void runningChanged(); void pidChanged(); + void workingDirectoryChanged(); void commandChanged(); void stdoutParserChanged(); void stderrParserChanged(); @@ -108,11 +120,13 @@ private slots: void onStderrReadyRead(); void onStdoutParserDestroyed(); void onStderrParserDestroyed(); + void onGlobalWorkingDirectoryChanged(); private: void startProcessIfReady(); QProcess* process = nullptr; + QString mWorkingDirectory; QList mCommand; DataStreamParser* mStdoutParser = nullptr; DataStreamParser* mStderrParser = nullptr;