#pragma once #include #include #include #include #include #include #include #include #include #include "../core/doc.hpp" #include "../core/reload.hpp" #include "datastream.hpp" #include "processcore.hpp" // Needed when compiling with clang musl-libc++. // Default include paths contain macros that cause name collisions. #undef stdout #undef stderr ///! Child process. /// #### Example /// ```qml /// Process { /// running: true /// command: [ "some-command", "arg" ] /// stdout: @@StdioCollector { /// onStreamFinished: console.log(`line read: ${this.text}`) /// } /// } /// ``` class Process: public PostReloadHook { Q_OBJECT; // clang-format off /// If the process is currently running. Defaults to false. /// /// Setting this property to true will start the process if command has at least /// one element. /// Setting it to false will send SIGTERM. To immediately kill the process, /// use @@signal() with SIGKILL. The process will be killed when /// quickshell dies. /// /// If you want to run the process in a loop, use the onRunningChanged signal handler /// to restart the process. /// ```qml /// Process { /// running: true /// onRunningChanged: if (!running) running = true /// } /// ``` /// /// > [!NOTE] See @@startDetached() to prevent the process from being killed by Quickshell /// > if Quickshell is killed or the configuration is reloaded. 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 processId READ processId NOTIFY processIdChanged); /// 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 /// return the new value, not the one for the currently running process. /// /// > [!WARNING] This does not run command in a shell. All arguments to the command /// > must be in separate values in the list, e.g. `["echo", "hello"]` /// > and not `["echo hello"]`. /// > /// > Additionally, shell scripts must be run by your shell, /// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script /// > has a shebang. /// /// > [!INFO] You can use `["sh", "-c", ]` to execute your command with /// > the system shell. Q_PROPERTY(QList command READ command WRITE setCommand NOTIFY commandChanged); /// 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); /// Environment of the executed process. /// /// This is a javascript object (json). Environment variables can be added by setting /// them to a string and removed by setting them to null (except when @@clearEnvironment is true, /// in which case this behavior is inverted, see @@clearEnvironment for details). /// /// /// ```qml /// environment: ({ /// ADDED: "value", /// REMOVED: null, /// "i'm different": "value", /// }) /// ``` /// /// > [!INFO] You need to wrap the returned object in () otherwise it won't parse due to javascript ambiguity. /// /// 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. Q_PROPERTY(QHash environment READ environment WRITE setEnvironment NOTIFY environmentChanged); /// If the process's environment should be cleared prior to applying @@environment. /// Defaults to false. /// /// If true, all environment variables will be removed before the @@environment /// object is applied, meaning the variables listed will be the only ones visible to the process. /// This changes the behavior of `null` to pass in the system value of the variable if present instead /// of removing it. /// /// ```qml /// clearEnvironment: true /// environment: ({ /// ADDED: "value", /// PASSED_FROM_SYSTEM: null, /// }) /// ``` /// /// 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. Q_PROPERTY(bool clearEnvironment READ environmentCleared WRITE setEnvironmentCleared NOTIFY environmentClearChanged); /// The parser for stdout. If the parser is null the process's stdout channel will be closed /// and no further data will be read, even if a new parser is attached. Q_PROPERTY(DataStreamParser* stdout READ stdoutParser WRITE setStdoutParser NOTIFY stdoutParserChanged); /// The parser for stderr. If the parser is null the process's stdout channel will be closed /// and no further data will be read, even if a new parser is attached. Q_PROPERTY(DataStreamParser* stderr READ stderrParser WRITE setStderrParser NOTIFY stderrParserChanged); /// If stdin is enabled. Defaults to false. If this property is false the process's stdin channel /// will be closed and @@write() will do nothing, even if set back to true. Q_PROPERTY(bool stdinEnabled READ stdinEnabled WRITE setStdinEnabled NOTIFY stdinEnabledChanged); // clang-format on QML_ELEMENT; public: explicit Process(QObject* parent = nullptr); ~Process() override; Q_DISABLE_COPY_MOVE(Process); void onPostReload() override; // MUST be before exec(ctx) or the other will be called with a default constructed obj. QSDOC_HIDE Q_INVOKABLE void exec(QList command); /// Launch a process with the given arguments, stopping any currently running process. /// /// The context parameter can either be a list of command arguments or a JS object with the following fields: /// - `command`: A list containing the command and all its arguments. See @@Quickshell.Io.Process.command. /// - `environment`: Changes to make to the process environment. See @@Quickshell.Io.Process.environment. /// - `clearEnvironment`: Removes all variables from the environment if true. /// - `workingDirectory`: The working directory the command should run in. /// /// Passed parameters will change the values currently set in the process. /// /// > [!WARNING] This does not run command in a shell. All arguments to the command /// > must be in separate values in the list, e.g. `["echo", "hello"]` /// > and not `["echo hello"]`. /// > /// > Additionally, shell scripts must be run by your shell, /// > e.g. `["sh", "script.sh"]` instead of `["script.sh"]` unless the script /// > has a shebang. /// /// > [!INFO] You can use `["sh", "-c", ]` to execute your command with /// > the system shell. /// /// Calling this function is equivalent to running: /// ```qml /// process.running = false; /// process.command = ... /// process.environment = ... /// process.clearEnvironment = ... /// process.workingDirectory = ... /// process.running = true; /// ``` Q_INVOKABLE void exec(const qs::io::process::ProcessContext& context); /// Sends a signal to the process if @@running is true, otherwise does nothing. Q_INVOKABLE void signal(qint32 signal); /// Writes to the process's stdin. Does nothing if @@running is false. Q_INVOKABLE void write(const QString& data); /// Launches an instance of the process detached from Quickshell. /// /// The subprocess will not be tracked, @@running will be false, /// and the subprocess will not be killed by Quickshell. /// /// This function is equivalent to @@Quickshell.Quickshell.execDetached(). Q_INVOKABLE void startDetached(); [[nodiscard]] bool isRunning() const; void setRunning(bool running); [[nodiscard]] QVariant processId() const; [[nodiscard]] QList command() const; void setCommand(QList command); [[nodiscard]] QString workingDirectory() const; void setWorkingDirectory(const QString& workingDirectory); [[nodiscard]] QHash environment() const; void setEnvironment(QHash environment); [[nodiscard]] bool environmentCleared() const; void setEnvironmentCleared(bool cleared); [[nodiscard]] DataStreamParser* stdoutParser() const; void setStdoutParser(DataStreamParser* parser); [[nodiscard]] DataStreamParser* stderrParser() const; void setStderrParser(DataStreamParser* parser); [[nodiscard]] bool stdinEnabled() const; void setStdinEnabled(bool enabled); signals: void started(); void exited(qint32 exitCode, QProcess::ExitStatus exitStatus); void runningChanged(); void processIdChanged(); void commandChanged(); void workingDirectoryChanged(); void environmentChanged(); void environmentClearChanged(); void stdoutParserChanged(); void stderrParserChanged(); void stdinEnabledChanged(); private slots: void onStarted(); void onFinished(qint32 exitCode, QProcess::ExitStatus exitStatus); void onErrorOccurred(QProcess::ProcessError error); void onStdoutReadyRead(); void onStderrReadyRead(); void onStdoutParserDestroyed(); void onStderrParserDestroyed(); void onGlobalWorkingDirectoryChanged(); private: void startProcessIfReady(); void setupEnvironment(QProcess* process); QProcess* process = nullptr; QList mCommand; QString mWorkingDirectory; QHash mEnvironment; DataStreamParser* mStdoutParser = nullptr; DataStreamParser* mStderrParser = nullptr; QByteArray stdoutBuffer; QByteArray stderrBuffer; bool targetRunning = false; bool mStdinEnabled = false; bool mClearEnvironment = false; };