Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/dde-am/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
85 changes: 85 additions & 0 deletions apps/dde-am/src/commandexecutor.cpp
Original file line number Diff line number Diff line change
@@ -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 <QDBusConnection>
#include <QDBusMetaType>
#include <QDBusMessage>

DCORE_USE_NAMESPACE

namespace {
void registerComplexDbusType()
{
qRegisterMetaType<ObjectInterfaceMap>();
qDBusRegisterMetaType<ObjectInterfaceMap>();
qRegisterMetaType<ObjectMap>();
qDBusRegisterMetaType<ObjectMap>();
qDBusRegisterMetaType<QStringMap>();
qRegisterMetaType<QStringMap>();
qRegisterMetaType<PropMap>();
qDBusRegisterMetaType<PropMap>();
qDBusRegisterMetaType<QDBusObjectPath>();
}
}

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<void> 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<QVariant> 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<int>(reply.type()),
QString("Failed to execute command: %1, error: %2").arg(m_program, reply.errorMessage())};
}
return {};
}
30 changes: 30 additions & 0 deletions apps/dde-am/src/commandexecutor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: 2025 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

#pragma once

#include <QString>
#include <QStringList>
#include <DExpected>

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<void> execute();

private:
QString m_program;
QStringList m_args;
QString m_type;
QString m_runId;
QString m_workdir;
QStringList m_environmentVariables;
};
182 changes: 143 additions & 39 deletions apps/dde-am/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
#include <QFileInfo>
#include <QUrl>

#include <DUtil>

#include "launcher.h"
#include "global.h"
#include <DUtil>
#include "commandexecutor.h"

DCORE_USE_NAMESPACE

Expand All @@ -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()) {
Expand All @@ -93,8 +120,6 @@ int main(int argc, char *argv[])
if (!arguments.isEmpty()) {
action = arguments.takeFirst();
}
} else {
parser.showHelp();
}

// Handle action - prioritize --action option
Expand All @@ -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);
}
Expand All @@ -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] <appId> Launch application by ID or path\n"
" dde-am -c <program> [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);
}
}
16 changes: 15 additions & 1 deletion docs/dde-am-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -60,6 +70,10 @@ dde-am --list
- `-e, --env <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 <program>`: Execute a command
- `--type <type>`: Type of execution (shortcut, script, portablebinary). Default: portablebinary
- `--run-id <runId>`: Run ID for the execution
- `--workdir <dir>`: Working directory for the execution
- `--help`: Show help information

## Features
Expand Down