From 71334bfcaf243a175bb7a96dfaf29863c8c217a0 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Sun, 13 Jul 2025 19:55:09 -0700 Subject: [PATCH] core/desktopentry: expose exec command and use execDetached on call --- src/core/common.cpp | 2 -- src/core/common.hpp | 2 +- src/core/desktopentry.cpp | 37 ++++++++++++++++----------------- src/core/desktopentry.hpp | 43 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 58 insertions(+), 26 deletions(-) diff --git a/src/core/common.cpp b/src/core/common.cpp index 5928e470..080019ab 100644 --- a/src/core/common.cpp +++ b/src/core/common.cpp @@ -1,11 +1,9 @@ #include "common.hpp" #include -#include namespace qs { const QDateTime Common::LAUNCH_TIME = QDateTime::currentDateTime(); -QProcessEnvironment Common::INITIAL_ENVIRONMENT = {}; // NOLINT } // namespace qs diff --git a/src/core/common.hpp b/src/core/common.hpp index f2a01bc1..ab8edb80 100644 --- a/src/core/common.hpp +++ b/src/core/common.hpp @@ -7,7 +7,7 @@ namespace qs { struct Common { static const QDateTime LAUNCH_TIME; - static QProcessEnvironment INITIAL_ENVIRONMENT; // NOLINT + static inline QProcessEnvironment INITIAL_ENVIRONMENT = {}; // NOLINT }; } // namespace qs diff --git a/src/core/desktopentry.cpp b/src/core/desktopentry.cpp index 3c4b6f26..46738812 100644 --- a/src/core/desktopentry.cpp +++ b/src/core/desktopentry.cpp @@ -11,14 +11,14 @@ #include #include #include -#include #include #include #include -#include "common.hpp" +#include "../io/processcore.hpp" #include "logcat.hpp" #include "model.hpp" +#include "qmlglobal.hpp" namespace { QS_LOGGING_CATEGORY(logDesktopEntry, "quickshell.desktopentry", QtWarningMsg); @@ -111,8 +111,10 @@ void DesktopEntry::parseEntry(const QString& text) { else if (key == "NoDisplay") this->mNoDisplay = value == "true"; else if (key == "Comment") this->mComment = value; else if (key == "Icon") this->mIcon = value; - else if (key == "Exec") this->mExecString = value; - else if (key == "Path") this->mWorkingDirectory = value; + else if (key == "Exec") { + this->mExecString = value; + this->mCommand = DesktopEntry::parseExecString(value); + } else if (key == "Path") this->mWorkingDirectory = value; else if (key == "Terminal") this->mTerminal = value == "true"; else if (key == "Categories") this->mCategories = value.split(u';', Qt::SkipEmptyParts); else if (key == "Keywords") this->mKeywords = value.split(u';', Qt::SkipEmptyParts); @@ -127,7 +129,10 @@ void DesktopEntry::parseEntry(const QString& text) { if (key == "Name") action->mName = value; else if (key == "Icon") action->mIcon = value; - else if (key == "Exec") action->mExecString = value; + else if (key == "Exec") { + action->mExecString = value; + action->mCommand = DesktopEntry::parseExecString(value); + } } this->mActions.insert(actionName, action); @@ -179,7 +184,7 @@ void DesktopEntry::parseEntry(const QString& text) { } void DesktopEntry::execute() const { - DesktopEntry::doExec(this->mExecString, this->mWorkingDirectory); + DesktopEntry::doExec(this->mCommand, this->mWorkingDirectory); } bool DesktopEntry::isValid() const { return !this->mName.isEmpty(); } @@ -251,23 +256,15 @@ QVector DesktopEntry::parseExecString(const QString& execString) { return arguments; } -void DesktopEntry::doExec(const QString& execString, const QString& workingDirectory) { - auto args = DesktopEntry::parseExecString(execString); - if (args.isEmpty()) { - qCWarning(logDesktopEntry) << "Tried to exec string" << execString << "which parsed as empty."; - return; - } - - auto process = QProcess(); - process.setProgram(args.at(0)); - process.setArguments(args.sliced(1)); - if (!workingDirectory.isEmpty()) process.setWorkingDirectory(workingDirectory); - process.setProcessEnvironment(qs::Common::INITIAL_ENVIRONMENT); - process.startDetached(); +void DesktopEntry::doExec(const QList& execString, const QString& workingDirectory) { + qs::io::process::ProcessContext ctx; + ctx.setCommand(execString); + ctx.setWorkingDirectory(workingDirectory); + QuickshellGlobal::execDetached(ctx); } void DesktopAction::execute() const { - DesktopEntry::doExec(this->mExecString, this->entry->mWorkingDirectory); + DesktopEntry::doExec(this->mCommand, this->entry->mWorkingDirectory); } DesktopEntryManager::DesktopEntryManager() { diff --git a/src/core/desktopentry.hpp b/src/core/desktopentry.hpp index 3871181b..ee8f5112 100644 --- a/src/core/desktopentry.hpp +++ b/src/core/desktopentry.hpp @@ -28,8 +28,19 @@ class DesktopEntry: public QObject { Q_PROPERTY(QString comment MEMBER mComment CONSTANT); /// Name of the icon associated with this application. May be empty. Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); - /// The raw `Exec` string from the desktop entry. You probably want @@execute(). + /// The raw `Exec` string from the desktop entry. + /// + /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); + /// The parsed `Exec` command in the desktop entry. + /// + /// The entry can be run with @@execute(), or by using this command in + /// @@Quickshell.Quickshell.execDetached() or @@Quickshell.Io.Process. + /// If used in `execDetached` or a `Process`, @@workingDirectory should also be passed to + /// the invoked process. See @@execute() for details. + /// + /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); /// The working directory to execute from. Q_PROPERTY(QString workingDirectory MEMBER mWorkingDirectory CONSTANT); /// If the application should run in a terminal. @@ -46,6 +57,16 @@ public: void parseEntry(const QString& text); /// Run the application. Currently ignores @@runInTerminal and field codes. + /// + /// This is equivalent to calling @@Quickshell.Quickshell.execDetached() with @@command + /// and @@DesktopEntry.workingDirectory as shown below: + /// + /// ```qml + /// Quickshell.execDetached({ + /// command: desktopEntry.command, + /// workingDirectory: desktopEntry.workingDirectory, + /// }); + /// ``` Q_INVOKABLE void execute() const; [[nodiscard]] bool isValid() const; @@ -54,7 +75,7 @@ public: // currently ignores all field codes. static QVector parseExecString(const QString& execString); - static void doExec(const QString& execString, const QString& workingDirectory); + static void doExec(const QList& execString, const QString& workingDirectory); public: QString mId; @@ -64,6 +85,7 @@ public: QString mComment; QString mIcon; QString mExecString; + QVector mCommand; QString mWorkingDirectory; bool mTerminal = false; QVector mCategories; @@ -82,8 +104,19 @@ class DesktopAction: public QObject { Q_PROPERTY(QString id MEMBER mId CONSTANT); Q_PROPERTY(QString name MEMBER mName CONSTANT); Q_PROPERTY(QString icon MEMBER mIcon CONSTANT); - /// The raw `Exec` string from the desktop entry. You probably want @@execute(). + /// The raw `Exec` string from the action. + /// + /// > [!WARNING] This cannot be reliably run as a command. See @@command for one you can run. Q_PROPERTY(QString execString MEMBER mExecString CONSTANT); + /// The parsed `Exec` command in the action. + /// + /// The entry can be run with @@execute(), or by using this command in + /// @@Quickshell.Quickshell.execDetached() or @@Quickshell.Io.Process. + /// If used in `execDetached` or a `Process`, @@DesktopEntry.workingDirectory should also be passed to + /// the invoked process. + /// + /// > [!NOTE] The provided command does not invoke a terminal even if @@runInTerminal is true. + Q_PROPERTY(QVector command MEMBER mCommand CONSTANT); QML_ELEMENT; QML_UNCREATABLE("DesktopAction instances must be retrieved from a DesktopEntry"); @@ -94,6 +127,9 @@ public: , mId(std::move(id)) {} /// Run the application. Currently ignores @@DesktopEntry.runInTerminal and field codes. + /// + /// This is equivalent to calling @@Quickshell.Quickshell.execDetached() with @@command + /// and @@DesktopEntry.workingDirectory. Q_INVOKABLE void execute() const; private: @@ -102,6 +138,7 @@ private: QString mName; QString mIcon; QString mExecString; + QVector mCommand; QHash mEntries; friend class DesktopEntry;