From d793622d12d7df99e442e7490ebedcf9d3e3a47a Mon Sep 17 00:00:00 2001 From: zhaoyingzhen Date: Mon, 22 Dec 2025 13:47:51 +0800 Subject: [PATCH] feat: add command execution support to dde-am - Add CommandExecutor class for executing arbitrary commands via DBus - Refactor main() into handleList, handleExecuteCommand, handleLaunchApp - Add --command/-c option with --type, --run-id, --workdir parameters - Add validation to require appId when not using --command - Improve help text with Usage section and examples - Update documentation with command execution examples Log: add command execution support to dde-am --- apps/dde-am/src/CMakeLists.txt | 1 + apps/dde-am/src/commandexecutor.cpp | 85 +++++++++++++ apps/dde-am/src/commandexecutor.h | 30 +++++ apps/dde-am/src/main.cpp | 182 ++++++++++++++++++++++------ docs/dde-am-usage.md | 16 ++- 5 files changed, 274 insertions(+), 40 deletions(-) create mode 100644 apps/dde-am/src/commandexecutor.cpp create mode 100644 apps/dde-am/src/commandexecutor.h diff --git a/apps/dde-am/src/CMakeLists.txt b/apps/dde-am/src/CMakeLists.txt index 4190c82d..6a06d662 100644 --- a/apps/dde-am/src/CMakeLists.txt +++ b/apps/dde-am/src/CMakeLists.txt @@ -2,6 +2,7 @@ set(BIN_NAME dde-am) add_executable(${BIN_NAME} main.cpp launcher.h launcher.cpp + commandexecutor.h commandexecutor.cpp ) target_link_libraries(${BIN_NAME} PRIVATE diff --git a/apps/dde-am/src/commandexecutor.cpp b/apps/dde-am/src/commandexecutor.cpp new file mode 100644 index 00000000..47bd2ea1 --- /dev/null +++ b/apps/dde-am/src/commandexecutor.cpp @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "commandexecutor.h" +#include "global.h" + +#include +#include +#include + +DCORE_USE_NAMESPACE + +namespace { +void registerComplexDbusType() +{ + qRegisterMetaType(); + qDBusRegisterMetaType(); + qRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} +} + +void CommandExecutor::setProgram(const QString &program) +{ + m_program = program; +} + +void CommandExecutor::setArguments(const QStringList &arguments) +{ + m_args = arguments; +} + +void CommandExecutor::setType(const QString &type) +{ + m_type = type; +} + +void CommandExecutor::setRunId(const QString &runId) +{ + m_runId = runId; +} + +void CommandExecutor::setWorkDir(const QString &workdir) +{ + m_workdir = workdir; +} + +void CommandExecutor::setEnvironmentVariables(const QStringList &envVars) +{ + m_environmentVariables = envVars; +} + +DExpected CommandExecutor::execute() +{ + registerComplexDbusType(); + auto con = QDBusConnection::sessionBus(); + auto msg = QDBusMessage::createMethodCall( + DDEApplicationManager1ServiceName, DDEApplicationManager1ObjectPath, ApplicationManager1Interface, "executeCommand"); + + QStringMap envMap; + for (const auto &env : std::as_const(m_environmentVariables)) { + int idx = env.indexOf('='); + if (idx > 0) { + envMap.insert(env.left(idx), env.mid(idx + 1)); + } + } + + QList arguments; + arguments << m_program << m_args << m_type << m_runId << QVariant::fromValue(envMap) << m_workdir; + + msg.setArguments(arguments); + auto reply = con.call(msg); + if (reply.type() != QDBusMessage::ReplyMessage) { + return DUnexpected{emplace_tag::USE_EMPLACE, + static_cast(reply.type()), + QString("Failed to execute command: %1, error: %2").arg(m_program, reply.errorMessage())}; + } + return {}; +} diff --git a/apps/dde-am/src/commandexecutor.h b/apps/dde-am/src/commandexecutor.h new file mode 100644 index 00000000..bc9ba1eb --- /dev/null +++ b/apps/dde-am/src/commandexecutor.h @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class CommandExecutor +{ +public: + void setProgram(const QString &program); + void setArguments(const QStringList &arguments); + void setType(const QString &type); + void setRunId(const QString &runId); + void setWorkDir(const QString &workdir); + void setEnvironmentVariables(const QStringList &envVars); + + Dtk::Core::DExpected execute(); + +private: + QString m_program; + QStringList m_args; + QString m_type; + QString m_runId; + QString m_workdir; + QStringList m_environmentVariables; +}; diff --git a/apps/dde-am/src/main.cpp b/apps/dde-am/src/main.cpp index be252050..6185358e 100644 --- a/apps/dde-am/src/main.cpp +++ b/apps/dde-am/src/main.cpp @@ -8,9 +8,11 @@ #include #include +#include + #include "launcher.h" #include "global.h" -#include +#include "commandexecutor.h" DCORE_USE_NAMESPACE @@ -37,53 +39,78 @@ QString getAppIdFromInput(const QString &input) return {}; } -int main(int argc, char *argv[]) +int handleList() { - QCoreApplication app{argc, argv}; + const auto apps = Launcher::appIds(); + if (!apps) { + qWarning() << apps.error(); + return apps.error().getErrorCode(); + } - QCommandLineParser parser; - parser.addHelpOption(); - parser.addVersionOption(); + for (const auto &item : apps.value()) { + qDebug() << qPrintable(item); + } - QCommandLineOption listOption("list", "List all appId."); - parser.addOption(listOption); - QCommandLineOption launchedByUserOption("by-user", - "Launched by user, it's useful for counting launched times."); - parser.addOption(launchedByUserOption); - QCommandLineOption autostartOption("autostart", - "Launched by system autostart. Suppress prelaunch splash."); - parser.addOption(autostartOption); - QCommandLineOption actionOption(QStringList() << "a" << "action", - "Specify action identifier for the application", "action"); - parser.addOption(actionOption); - QCommandLineOption envOption(QStringList() << "e" << "env", - "Set environment variable, format: NAME=VALUE (can be used multiple times)", "env"); - parser.addOption(envOption); - - parser.addPositionalArgument("appId", "Application's ID, .desktop file path, or URI (e.g.: file:///path/to/app.desktop).");; + return 0; +} - parser.process(app); - if (parser.isSet(listOption)) { - const auto apps = Launcher::appIds(); - if (!apps) { - qWarning() << apps.error(); - return apps.error().getErrorCode(); - } - for (const auto &item :apps.value()) { - qDebug() << qPrintable(item); - } - return 0; +int handleExecuteCommand(const QCommandLineParser &parser, + const QCommandLineOption &executeOption, + const QCommandLineOption &typeOption, + const QCommandLineOption &runIdOption, + const QCommandLineOption &workdirOption, + const QCommandLineOption &envOption) +{ + CommandExecutor executor; + executor.setProgram(parser.value(executeOption)); + + if (parser.isSet(typeOption)) { + executor.setType(parser.value(typeOption)); + } else { + executor.setType("portablebinary"); } + if (parser.isSet(runIdOption)) { + executor.setRunId(parser.value(runIdOption)); + } + + if (parser.isSet(workdirOption)) { + executor.setWorkDir(parser.value(workdirOption)); + } else { + executor.setWorkDir(QDir::currentPath()); + } + + if (parser.isSet(envOption)) { + executor.setEnvironmentVariables(parser.values(envOption)); + } + + auto args = parser.positionalArguments(); + executor.setArguments(args); + + auto ret = executor.execute(); + if (!ret) { + qWarning() << ret.error(); + return ret.error().getErrorCode(); + } + + return 0; +} + +int handleLaunchApp(const QCommandLineParser &parser, + const QCommandLineOption &envOption, + const QCommandLineOption &actionOption, + const QCommandLineOption &launchedByUserOption, + const QCommandLineOption &autostartOption) +{ QString appId; QString action; QStringList envVars; - + // Handle environment variables - prioritize -e/--env option if (parser.isSet(envOption)) { envVars.append(parser.values(envOption)); } - + auto arguments = parser.positionalArguments(); QString inputArg; if (!arguments.isEmpty()) { @@ -93,8 +120,6 @@ int main(int argc, char *argv[]) if (!arguments.isEmpty()) { action = arguments.takeFirst(); } - } else { - parser.showHelp(); } // Handle action - prioritize --action option @@ -120,11 +145,11 @@ int main(int argc, char *argv[]) appPath = inputArg; } launcher.setPath(appPath); - + if (!action.isEmpty()) { launcher.setAction(action); } - + if (!envVars.isEmpty()) { launcher.setEnvironmentVariables(envVars); } @@ -134,5 +159,84 @@ int main(int argc, char *argv[]) qWarning() << ret.error(); return ret.error().getErrorCode(); } + return 0; } + +int main(int argc, char *argv[]) +{ + QCoreApplication app{argc, argv}; + + QCommandLineParser parser; + parser.setApplicationDescription("Deepin Application Manager Client\n\n" + "Usage:\n" + " dde-am [options] Launch application by ID or path\n" + " dde-am -c [args...] Execute a command with arguments\n" + " dde-am --list List all installed applications\n\n" + "Examples:\n" + " dde-am org.deepin.calculator # Launch application by ID\n" + " dde-am -c /usr/bin/ls -- -l # Execute command with arguments"); + parser.addHelpOption(); + parser.addVersionOption(); + + // Application Launching Options + QCommandLineOption listOption("list", "List all installed application IDs."); + parser.addOption(listOption); + QCommandLineOption launchedByUserOption("by-user", + "Mark launch as initiated by user (useful for usage statistics)."); + parser.addOption(launchedByUserOption); + QCommandLineOption autostartOption("autostart", + "Launched by system autostart. Suppress prelaunch splash."); + parser.addOption(autostartOption); + QCommandLineOption actionOption(QStringList() << "a" << "action", + "Specify action identifier to trigger for the application.", "action"); + parser.addOption(actionOption); + + // Common Options + QCommandLineOption envOption(QStringList() << "e" << "env", + "Set environment variable, format: NAME=VALUE (can be used multiple times).", "env"); + parser.addOption(envOption); + + // Command Execution Options + QCommandLineOption executeOption(QStringList() << "c" << "command", + "Execute a command/program directly.", "program"); + parser.addOption(executeOption); + + QCommandLineOption typeOption("type", "Execution type (shortcut, script, portablebinary). Default: portablebinary.\n" + "Only valid with --command.", "type"); + parser.addOption(typeOption); + + QCommandLineOption runIdOption("run-id", "Custom Run ID for the execution.\n" + "Only valid with --command.", "runId"); + parser.addOption(runIdOption); + + QCommandLineOption workdirOption("workdir", "Working directory for the execution.\n" + "Only valid with --command.", "dir"); + parser.addOption(workdirOption); + + parser.addPositionalArgument("appId", "Application ID, .desktop file path, or URI to launch.\n" + "If --command is specified, this acts as arguments for the command " + "(use '--' before arguments starting with '-')."); + + parser.process(app); + + // Validate mode and arguments + const bool hasCommand = parser.isSet(executeOption); + const QStringList posArgs = parser.positionalArguments(); + + if (parser.isSet(listOption)) { + return handleList(); + } + + if (hasCommand) { + // Command mode: positional args are command arguments + return handleExecuteCommand(parser, executeOption, typeOption, runIdOption, workdirOption, envOption); + } else { + // Launch mode: positional args must contain appId + if (posArgs.isEmpty()) { + qCritical() << "Error: Missing appId. Use --help for usage information."; + return 1; + } + return handleLaunchApp(parser, envOption, actionOption, launchedByUserOption, autostartOption); + } +} diff --git a/docs/dde-am-usage.md b/docs/dde-am-usage.md index 9e021911..f9698770 100644 --- a/docs/dde-am-usage.md +++ b/docs/dde-am-usage.md @@ -48,7 +48,17 @@ dde-am deepin-terminal quake-mode dde-am --by-user dde-calendar ``` -### 7. List all available app IDs +### 7. Execute arbitrary command + +```bash +# Execute a command with arguments +dde-am -c /usr/bin/ls -- -l -h + +# Execute a script with specific options +dde-am -c /path/to/script.sh --type script --workdir /home/user --run-id my-script +``` + +### 8. List all available app IDs ```bash dde-am --list @@ -60,6 +70,10 @@ dde-am --list - `-e, --env `: Set environment variable, format: NAME=VALUE (can be used multiple times) - `--by-user`: Mark as launched by user (useful for counting launched times) - `--list`: List all available app IDs +- `-c, --command `: Execute a command +- `--type `: Type of execution (shortcut, script, portablebinary). Default: portablebinary +- `--run-id `: Run ID for the execution +- `--workdir `: Working directory for the execution - `--help`: Show help information ## Features