feat: support application hooks
Signed-off-by: ComixHe <heyuming@deepin.org>
This commit is contained in:
parent
f233279466
commit
fb0fc0a8ee
29
docs/applicationHooks.md
Normal file
29
docs/applicationHooks.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Application Hooks
|
||||||
|
|
||||||
|
本文档描述了 dde-application-manager 的hook机制。
|
||||||
|
|
||||||
|
## 功能描述
|
||||||
|
|
||||||
|
hook 允许系统组件在应用启动前对应用的运行时环境做出配置(如cgroups)。
|
||||||
|
|
||||||
|
## 配置文件
|
||||||
|
|
||||||
|
hook 的配置文件需要放在'/usr/share/deepin/dde-application-manager/hook.d/'下,文件名必须符合以下规范:
|
||||||
|
- 以数字开头,作为hook的顺序标识。
|
||||||
|
- 以`-`分割顺序和hook名。
|
||||||
|
- 文件格式需要是`json`,文件扩展名同样以`json`结尾。
|
||||||
|
|
||||||
|
例如: `1-proxy.json`就是一个符合要求的hook配置文件。
|
||||||
|
|
||||||
|
### 文件格式
|
||||||
|
|
||||||
|
文件中需要写明hook二进制的绝对位置和所需要的参数,例如:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Exec": "/usr/bin/proxy",
|
||||||
|
"Args": ["--protocol=https","--port=12345"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
需要注意的是,配置文件的键是大小写敏感的。
|
66
src/applicationHooks.cpp
Normal file
66
src/applicationHooks.cpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "applicationHooks.h"
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonParseError>
|
||||||
|
|
||||||
|
std::optional<ApplicationHook> ApplicationHook::loadFromFile(const QString &filePath) noexcept
|
||||||
|
{
|
||||||
|
auto index = filePath.lastIndexOf(QDir::separator());
|
||||||
|
auto fileName = filePath.last(filePath.size() - index - 1);
|
||||||
|
|
||||||
|
QFile file{filePath};
|
||||||
|
if (!file.open(QFile::Text | QFile::ReadOnly | QFile::ExistingOnly)) {
|
||||||
|
qWarning() << "open hook file:" << filePath << "failed:" << file.errorString() << ", skip.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto content = file.readAll();
|
||||||
|
QJsonParseError err;
|
||||||
|
auto json = QJsonDocument::fromJson(content, &err);
|
||||||
|
if (err.error != QJsonParseError::NoError) {
|
||||||
|
qWarning() << "parse hook failed:" << err.errorString();
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (json.isEmpty()) {
|
||||||
|
qWarning() << "empty hook, skip.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto obj = json.object();
|
||||||
|
|
||||||
|
auto it = obj.constFind("Exec");
|
||||||
|
if (it == obj.constEnd()) {
|
||||||
|
qWarning() << "invalid hook: lack of Exec.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto exec = it->toString();
|
||||||
|
QFileInfo info{exec};
|
||||||
|
if (!(info.exists() and info.isExecutable())) {
|
||||||
|
qWarning() << "exec maybe doesn't exists or be executed.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
it = obj.constFind("Args");
|
||||||
|
if (it == obj.constEnd()) {
|
||||||
|
qWarning() << "invalid hook: lack of Args.";
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
auto args = it->toVariant().toStringList();
|
||||||
|
|
||||||
|
return ApplicationHook{std::move(fileName), std::move(exec), std::move(args)};
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList generateHooks(const QList<ApplicationHook> &hooks) noexcept
|
||||||
|
{
|
||||||
|
QStringList ret;
|
||||||
|
for (const auto &hook : hooks) {
|
||||||
|
ret.append(hook.execPath());
|
||||||
|
ret.append(hook.args());
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
39
src/applicationHooks.h
Normal file
39
src/applicationHooks.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
#ifndef APPLICATIONHOOKS_H
|
||||||
|
#define APPLICATIONHOOKS_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
class ApplicationHook
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static std::optional<ApplicationHook> loadFromFile(const QString &filePath) noexcept;
|
||||||
|
|
||||||
|
[[nodiscard]] const QString &hookName() const noexcept { return m_hookName; }
|
||||||
|
[[nodiscard]] const QString &execPath() const noexcept { return m_execPath; }
|
||||||
|
[[nodiscard]] const QStringList &args() const noexcept { return m_args; }
|
||||||
|
|
||||||
|
friend bool operator>(const ApplicationHook &lhs, const ApplicationHook &rhs) { return lhs.m_hookName > rhs.m_hookName; };
|
||||||
|
friend bool operator<(const ApplicationHook &lhs, const ApplicationHook &rhs) { return lhs.m_hookName < rhs.m_hookName; };
|
||||||
|
friend bool operator==(const ApplicationHook &lhs, const ApplicationHook &rhs) { return lhs.m_hookName == rhs.m_hookName; };
|
||||||
|
friend bool operator!=(const ApplicationHook &lhs, const ApplicationHook &rhs) { return !(lhs == rhs); };
|
||||||
|
|
||||||
|
private:
|
||||||
|
ApplicationHook(QString &&hookName, QString &&execPath, QStringList &&args)
|
||||||
|
: m_hookName(hookName)
|
||||||
|
, m_execPath(std::move(execPath))
|
||||||
|
, m_args(std::move(args))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
QString m_hookName;
|
||||||
|
QString m_execPath;
|
||||||
|
QStringList m_args;
|
||||||
|
};
|
||||||
|
|
||||||
|
QStringList generateHooks(const QList<ApplicationHook> &hooks) noexcept;
|
||||||
|
|
||||||
|
#endif
|
@ -51,4 +51,6 @@ constexpr auto STORAGE_VERSION = 0;
|
|||||||
constexpr auto ApplicationPropertiesGroup = u8"Application Properties";
|
constexpr auto ApplicationPropertiesGroup = u8"Application Properties";
|
||||||
constexpr auto LastLaunchedTime = u8"LastLaunchedTime";
|
constexpr auto LastLaunchedTime = u8"LastLaunchedTime";
|
||||||
|
|
||||||
|
constexpr auto ApplicationManagerHookDir = u8"/deepin/dde-application-manager/hooks.d";
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "dbus/AMobjectmanager1adaptor.h"
|
#include "dbus/AMobjectmanager1adaptor.h"
|
||||||
#include "systemdsignaldispatcher.h"
|
#include "systemdsignaldispatcher.h"
|
||||||
#include "propertiesForwarder.h"
|
#include "propertiesForwarder.h"
|
||||||
|
#include "applicationHooks.h"
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDBusMessage>
|
#include <QDBusMessage>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
@ -70,6 +71,8 @@ void ApplicationManager1Service::initService(QDBusConnection &connection) noexce
|
|||||||
|
|
||||||
scanInstances();
|
scanInstances();
|
||||||
|
|
||||||
|
loadHooks();
|
||||||
|
|
||||||
if (auto *ptr = new (std::nothrow) PropertiesForwarder{DDEApplicationManager1ObjectPath, this}; ptr == nullptr) {
|
if (auto *ptr = new (std::nothrow) PropertiesForwarder{DDEApplicationManager1ObjectPath, this}; ptr == nullptr) {
|
||||||
qCritical() << "new PropertiesForwarder of Application Manager failed.";
|
qCritical() << "new PropertiesForwarder of Application Manager failed.";
|
||||||
}
|
}
|
||||||
@ -209,18 +212,23 @@ void ApplicationManager1Service::scanApplications() noexcept
|
|||||||
{
|
{
|
||||||
const auto &desktopFileDirs = getDesktopFileDirs();
|
const auto &desktopFileDirs = getDesktopFileDirs();
|
||||||
|
|
||||||
applyIteratively(QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()), [this](const QFileInfo &info) -> bool {
|
applyIteratively(
|
||||||
DesktopErrorCode err{DesktopErrorCode::NoError};
|
QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
|
||||||
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
[this](const QFileInfo &info) -> bool {
|
||||||
if (!ret.has_value()) {
|
DesktopErrorCode err{DesktopErrorCode::NoError};
|
||||||
qWarning() << "failed to search File:" << err;
|
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
||||||
return false;
|
if (!ret.has_value()) {
|
||||||
}
|
qWarning() << "failed to search File:" << err;
|
||||||
if (!this->addApplication(std::move(ret).value())) {
|
return false;
|
||||||
qWarning() << "add Application failed, skip...";
|
}
|
||||||
}
|
if (!this->addApplication(std::move(ret).value())) {
|
||||||
return false; // means to apply this function to the rest of the files
|
qWarning() << "add Application failed, skip...";
|
||||||
});
|
}
|
||||||
|
return false; // means to apply this function to the rest of the files
|
||||||
|
},
|
||||||
|
QDir::Readable | QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot,
|
||||||
|
{"*.desktop"},
|
||||||
|
QDir::Name | QDir::DirsLast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApplicationManager1Service::scanInstances() noexcept
|
void ApplicationManager1Service::scanInstances() noexcept
|
||||||
@ -250,12 +258,17 @@ void ApplicationManager1Service::scanAutoStart() noexcept
|
|||||||
{
|
{
|
||||||
auto autostartDirs = getAutoStartDirs();
|
auto autostartDirs = getAutoStartDirs();
|
||||||
QStringList needToLaunch;
|
QStringList needToLaunch;
|
||||||
applyIteratively(QList<QDir>{autostartDirs.cbegin(), autostartDirs.cend()}, [&needToLaunch](const QFileInfo &info) {
|
applyIteratively(
|
||||||
if (info.isSymbolicLink()) {
|
QList<QDir>{autostartDirs.cbegin(), autostartDirs.cend()},
|
||||||
needToLaunch.emplace_back(info.symLinkTarget());
|
[&needToLaunch](const QFileInfo &info) {
|
||||||
}
|
if (info.isSymbolicLink()) {
|
||||||
return false;
|
needToLaunch.emplace_back(info.symLinkTarget());
|
||||||
});
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
QDir::Readable | QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot,
|
||||||
|
{"*.desktop"},
|
||||||
|
QDir::Name | QDir::DirsLast);
|
||||||
|
|
||||||
while (!needToLaunch.isEmpty()) {
|
while (!needToLaunch.isEmpty()) {
|
||||||
const auto &filePath = needToLaunch.takeFirst();
|
const auto &filePath = needToLaunch.takeFirst();
|
||||||
@ -269,6 +282,32 @@ void ApplicationManager1Service::scanAutoStart() noexcept
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ApplicationManager1Service::loadHooks() noexcept
|
||||||
|
{
|
||||||
|
auto hookDirs = getXDGDataDirs();
|
||||||
|
std::for_each(hookDirs.begin(), hookDirs.end(), [](QString &str) { str.append(ApplicationManagerHookDir); });
|
||||||
|
QHash<QString, ApplicationHook> hooks;
|
||||||
|
|
||||||
|
applyIteratively(
|
||||||
|
QList<QDir>(hookDirs.begin(), hookDirs.end()),
|
||||||
|
[&hooks](const QFileInfo &info) -> bool {
|
||||||
|
auto fileName = info.fileName();
|
||||||
|
if (!hooks.contains(fileName)) {
|
||||||
|
if (auto hook = ApplicationHook::loadFromFile(info.absoluteFilePath()); hook) {
|
||||||
|
hooks.insert(fileName, std::move(hook).value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks | QDir::Readable,
|
||||||
|
{"*.json"},
|
||||||
|
QDir::Name);
|
||||||
|
|
||||||
|
auto hookList = hooks.values();
|
||||||
|
std::sort(hookList.begin(), hookList.end());
|
||||||
|
m_hookElements = generateHooks(hookList);
|
||||||
|
}
|
||||||
|
|
||||||
QList<QDBusObjectPath> ApplicationManager1Service::list() const
|
QList<QDBusObjectPath> ApplicationManager1Service::list() const
|
||||||
{
|
{
|
||||||
return m_applicationList.keys();
|
return m_applicationList.keys();
|
||||||
@ -433,34 +472,39 @@ void ApplicationManager1Service::ReloadApplications()
|
|||||||
|
|
||||||
auto apps = m_applicationList.keys();
|
auto apps = m_applicationList.keys();
|
||||||
|
|
||||||
applyIteratively(QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()), [this, &apps](const QFileInfo &info) -> bool {
|
applyIteratively(
|
||||||
DesktopErrorCode err{DesktopErrorCode::NoError};
|
QList<QDir>(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
|
||||||
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
[this, &apps](const QFileInfo &info) -> bool {
|
||||||
if (!ret.has_value()) {
|
DesktopErrorCode err{DesktopErrorCode::NoError};
|
||||||
|
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
|
||||||
|
if (!ret.has_value()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto file = std::move(ret).value();
|
||||||
|
|
||||||
|
auto destApp =
|
||||||
|
std::find_if(m_applicationList.cbegin(),
|
||||||
|
m_applicationList.cend(),
|
||||||
|
[&file](const QSharedPointer<ApplicationService> &app) { return file.desktopId() == app->id(); });
|
||||||
|
|
||||||
|
if (err != DesktopErrorCode::NoError) {
|
||||||
|
qWarning() << "error occurred:" << err << " skip this application.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (destApp != m_applicationList.cend() and apps.contains(destApp.key())) {
|
||||||
|
apps.removeOne(destApp.key());
|
||||||
|
updateApplication(destApp.value(), std::move(file));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
addApplication(std::move(file));
|
||||||
return false;
|
return false;
|
||||||
}
|
},
|
||||||
|
QDir::Readable | QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot,
|
||||||
auto file = std::move(ret).value();
|
{"*.desktop"},
|
||||||
|
QDir::Name | QDir::DirsLast);
|
||||||
auto destApp =
|
|
||||||
std::find_if(m_applicationList.cbegin(),
|
|
||||||
m_applicationList.cend(),
|
|
||||||
[&file](const QSharedPointer<ApplicationService> &app) { return file.desktopId() == app->id(); });
|
|
||||||
|
|
||||||
if (err != DesktopErrorCode::NoError) {
|
|
||||||
qWarning() << "error occurred:" << err << " skip this application.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (destApp != m_applicationList.cend() and apps.contains(destApp.key())) {
|
|
||||||
apps.removeOne(destApp.key());
|
|
||||||
updateApplication(destApp.value(), std::move(file));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
addApplication(std::move(file));
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const auto &key : apps) {
|
for (const auto &key : apps) {
|
||||||
removeOneApplication(key);
|
removeOneApplication(key);
|
||||||
|
@ -41,7 +41,9 @@ public:
|
|||||||
|
|
||||||
void updateApplication(const QSharedPointer<ApplicationService> &destApp, DesktopFile desktopFile) noexcept;
|
void updateApplication(const QSharedPointer<ApplicationService> &destApp, DesktopFile desktopFile) noexcept;
|
||||||
|
|
||||||
JobManager1Service &jobManager() noexcept { return *m_jobManager; }
|
[[nodiscard]] JobManager1Service &jobManager() noexcept { return *m_jobManager; }
|
||||||
|
[[nodiscard]] const JobManager1Service &jobManager() const noexcept { return *m_jobManager; }
|
||||||
|
[[nodiscard]] const QStringList &applicationHooks() const noexcept { return m_hookElements; }
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
QString Identify(const QDBusUnixFileDescriptor &pidfd,
|
QString Identify(const QDBusUnixFileDescriptor &pidfd,
|
||||||
@ -59,11 +61,13 @@ private:
|
|||||||
std::unique_ptr<Identifier> m_identifier;
|
std::unique_ptr<Identifier> m_identifier;
|
||||||
std::weak_ptr<ApplicationManager1Storage> m_storage;
|
std::weak_ptr<ApplicationManager1Storage> m_storage;
|
||||||
QScopedPointer<JobManager1Service> m_jobManager{nullptr};
|
QScopedPointer<JobManager1Service> m_jobManager{nullptr};
|
||||||
|
QStringList m_hookElements;
|
||||||
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>> m_applicationList;
|
QMap<QDBusObjectPath, QSharedPointer<ApplicationService>> m_applicationList;
|
||||||
|
|
||||||
void scanApplications() noexcept;
|
void scanApplications() noexcept;
|
||||||
void scanInstances() noexcept;
|
void scanInstances() noexcept;
|
||||||
void scanAutoStart() noexcept;
|
void scanAutoStart() noexcept;
|
||||||
|
void loadHooks() noexcept;
|
||||||
void addInstanceToApplication(const QString &unitName, const QDBusObjectPath &systemdUnitPath);
|
void addInstanceToApplication(const QString &unitName, const QDBusObjectPath &systemdUnitPath);
|
||||||
void removeInstanceFromApplication(const QString &unitName, const QDBusObjectPath &systemdUnitPath);
|
void removeInstanceFromApplication(const QString &unitName, const QDBusObjectPath &systemdUnitPath);
|
||||||
};
|
};
|
||||||
|
@ -180,6 +180,10 @@ QDBusObjectPath ApplicationService::Launch(const QString &action, const QStringL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optionsMap.remove("_hooks"); // this is internal property, user shouldn't pass it to Application Manager
|
||||||
|
if (const auto &hooks = parent()->applicationHooks(); !hooks.isEmpty()) {
|
||||||
|
optionsMap.insert("_hooks", hooks);
|
||||||
|
}
|
||||||
auto cmds = generateCommand(optionsMap);
|
auto cmds = generateCommand(optionsMap);
|
||||||
|
|
||||||
auto [bin, execCmds, res] = unescapeExec(execStr, fields);
|
auto [bin, execCmds, res] = unescapeExec(execStr, fields);
|
||||||
@ -190,7 +194,7 @@ QDBusObjectPath ApplicationService::Launch(const QString &action, const QStringL
|
|||||||
}
|
}
|
||||||
|
|
||||||
cmds.append(std::move(execCmds));
|
cmds.append(std::move(execCmds));
|
||||||
auto &jobManager = static_cast<ApplicationManager1Service *>(parent())->jobManager();
|
auto &jobManager = parent()->jobManager();
|
||||||
return jobManager.addJob(
|
return jobManager.addJob(
|
||||||
m_applicationPath.path(),
|
m_applicationPath.path(),
|
||||||
[this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) mutable -> QVariant {
|
[this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) mutable -> QVariant {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include "applicationmanagerstorage.h"
|
#include "applicationmanagerstorage.h"
|
||||||
|
#include "dbus/applicationmanager1service.h"
|
||||||
#include "dbus/instanceservice.h"
|
#include "dbus/instanceservice.h"
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
#include "desktopentry.h"
|
#include "desktopentry.h"
|
||||||
@ -151,6 +152,11 @@ private:
|
|||||||
void updateAfterLaunch(bool isLaunch) noexcept;
|
void updateAfterLaunch(bool isLaunch) noexcept;
|
||||||
static bool shouldBeShown(const std::unique_ptr<DesktopEntry> &entry) noexcept;
|
static bool shouldBeShown(const std::unique_ptr<DesktopEntry> &entry) noexcept;
|
||||||
[[nodiscard]] bool autostartCheck(const QString &linkPath) const noexcept;
|
[[nodiscard]] bool autostartCheck(const QString &linkPath) const noexcept;
|
||||||
|
[[nodiscard]] ApplicationManager1Service *parent() { return dynamic_cast<ApplicationManager1Service *>(QObject::parent()); }
|
||||||
|
[[nodiscard]] const ApplicationManager1Service *parent() const
|
||||||
|
{
|
||||||
|
return dynamic_cast<ApplicationManager1Service *>(QObject::parent());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
18
src/global.h
18
src/global.h
@ -79,7 +79,11 @@ template <typename T>
|
|||||||
using remove_cvr_t = std::remove_reference_t<std::remove_cv_t<T>>;
|
using remove_cvr_t = std::remove_reference_t<std::remove_cv_t<T>>;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void applyIteratively(QList<QDir> dirs, T &&func)
|
void applyIteratively(QList<QDir> dirs,
|
||||||
|
T &&func,
|
||||||
|
QDir::Filters filter = QDir::NoFilter,
|
||||||
|
QStringList nameFilter = {},
|
||||||
|
QDir::SortFlags sortFlag = QDir::SortFlag::NoSort)
|
||||||
{
|
{
|
||||||
static_assert(std::is_invocable_v<T, const QFileInfo &>, "apply function should only accept one QFileInfo");
|
static_assert(std::is_invocable_v<T, const QFileInfo &>, "apply function should only accept one QFileInfo");
|
||||||
static_assert(std::is_same_v<decltype(func(QFileInfo{})), bool>,
|
static_assert(std::is_same_v<decltype(func(QFileInfo{})), bool>,
|
||||||
@ -93,9 +97,7 @@ void applyIteratively(QList<QDir> dirs, T &&func)
|
|||||||
qWarning() << "apply function to an non-existent directory:" << dir.absolutePath() << ", skip.";
|
qWarning() << "apply function to an non-existent directory:" << dir.absolutePath() << ", skip.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const auto &infoList = dir.entryInfoList(nameFilter, filter, sortFlag);
|
||||||
const auto &infoList = dir.entryInfoList(
|
|
||||||
{"*.desktop"}, QDir::Readable | QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot, QDir::Name | QDir::DirsLast);
|
|
||||||
|
|
||||||
for (const auto &info : infoList) {
|
for (const auto &info : infoList) {
|
||||||
if (info.isFile() and func(info)) {
|
if (info.isFile() and func(info)) {
|
||||||
@ -374,7 +376,7 @@ inline QString getXDGDataHome()
|
|||||||
return XDGDataHome;
|
return XDGDataHome;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline QStringList getDesktopFileDirs()
|
inline QStringList getXDGDataDirs()
|
||||||
{
|
{
|
||||||
auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts);
|
auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts);
|
||||||
|
|
||||||
@ -384,14 +386,18 @@ inline QStringList getDesktopFileDirs()
|
|||||||
}
|
}
|
||||||
|
|
||||||
XDGDataDirs.push_front(getXDGDataHome());
|
XDGDataDirs.push_front(getXDGDataHome());
|
||||||
|
return XDGDataDirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QStringList getDesktopFileDirs()
|
||||||
|
{
|
||||||
|
auto XDGDataDirs = getXDGDataDirs();
|
||||||
std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) {
|
std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) {
|
||||||
if (!str.endsWith(QDir::separator())) {
|
if (!str.endsWith(QDir::separator())) {
|
||||||
str.append(QDir::separator());
|
str.append(QDir::separator());
|
||||||
}
|
}
|
||||||
str.append("applications");
|
str.append("applications");
|
||||||
});
|
});
|
||||||
|
|
||||||
return XDGDataDirs;
|
return XDGDataDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,12 +11,14 @@
|
|||||||
QStringList generateCommand(const QVariantMap &props) noexcept
|
QStringList generateCommand(const QVariantMap &props) noexcept
|
||||||
{
|
{
|
||||||
std::vector<std::unique_ptr<LaunchOption>> options;
|
std::vector<std::unique_ptr<LaunchOption>> options;
|
||||||
for (auto it = props.constKeyValueBegin(); it!= props.constKeyValueEnd(); ++it) {
|
for (auto it = props.constKeyValueBegin(); it != props.constKeyValueEnd(); ++it) {
|
||||||
const auto &[key, value] = *it;
|
const auto &[key, value] = *it;
|
||||||
if (key == setUserLaunchOption::key()) {
|
if (key == setUserLaunchOption::key()) {
|
||||||
options.push_back(std::make_unique<setUserLaunchOption>(value));
|
options.emplace_back(std::make_unique<setUserLaunchOption>(value));
|
||||||
} else if (key == setEnvLaunchOption::key()) {
|
} else if (key == setEnvLaunchOption::key()) {
|
||||||
options.push_back(std::make_unique<setEnvLaunchOption>(value));
|
options.emplace_back(std::make_unique<setEnvLaunchOption>(value));
|
||||||
|
} else if (key == hookLaunchOption::key()) {
|
||||||
|
options.emplace_back(std::make_unique<hookLaunchOption>(value));
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "unsupported options" << key;
|
qWarning() << "unsupported options" << key;
|
||||||
}
|
}
|
||||||
@ -27,6 +29,10 @@ QStringList generateCommand(const QVariantMap &props) noexcept
|
|||||||
std::sort(options.begin(),
|
std::sort(options.begin(),
|
||||||
options.end(),
|
options.end(),
|
||||||
[](const std::unique_ptr<LaunchOption> &lOption, const std::unique_ptr<LaunchOption> &rOption) {
|
[](const std::unique_ptr<LaunchOption> &lOption, const std::unique_ptr<LaunchOption> &rOption) {
|
||||||
|
if (lOption->type() == rOption->type()) {
|
||||||
|
return lOption->m_priority >= rOption->m_priority;
|
||||||
|
}
|
||||||
|
|
||||||
if (lOption->type() == AppExecOption and rOption->type() == systemdOption) {
|
if (lOption->type() == AppExecOption and rOption->type() == systemdOption) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,11 @@ struct LaunchOption
|
|||||||
};
|
};
|
||||||
[[nodiscard]] virtual const QString &type() const noexcept = 0;
|
[[nodiscard]] virtual const QString &type() const noexcept = 0;
|
||||||
|
|
||||||
|
uint32_t m_priority{0};
|
||||||
QVariant m_val;
|
QVariant m_val;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
LaunchOption() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct setUserLaunchOption : public LaunchOption
|
struct setUserLaunchOption : public LaunchOption
|
||||||
@ -70,4 +74,24 @@ struct splitLaunchOption : public LaunchOption
|
|||||||
[[nodiscard]] QStringList generateCommandLine() const noexcept override;
|
[[nodiscard]] QStringList generateCommandLine() const noexcept override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct hookLaunchOption : public LaunchOption
|
||||||
|
{
|
||||||
|
explicit hookLaunchOption(QVariant v)
|
||||||
|
: LaunchOption(std::move(v))
|
||||||
|
{
|
||||||
|
m_priority = 1;
|
||||||
|
}
|
||||||
|
[[nodiscard]] const QString &type() const noexcept override
|
||||||
|
{
|
||||||
|
static QString tp{AppExecOption};
|
||||||
|
return tp;
|
||||||
|
}
|
||||||
|
[[nodiscard]] static const QString &key() noexcept
|
||||||
|
{
|
||||||
|
static QString hook{"hook"};
|
||||||
|
return hook;
|
||||||
|
}
|
||||||
|
[[nodiscard]] QStringList generateCommandLine() const noexcept override { return m_val.toStringList(); };
|
||||||
|
};
|
||||||
|
|
||||||
QStringList generateCommand(const QVariantMap &props) noexcept;
|
QStringList generateCommand(const QVariantMap &props) noexcept;
|
||||||
|
6
tests/data/hooks.d/1-test.json
Normal file
6
tests/data/hooks.d/1-test.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"Exec": "/usr/bin/echo",
|
||||||
|
"Args": [
|
||||||
|
"for test"
|
||||||
|
]
|
||||||
|
}
|
25
tests/ut_hook.cpp
Normal file
25
tests/ut_hook.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "applicationHooks.h"
|
||||||
|
#include <QDir>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(ApplicationHookTest, load)
|
||||||
|
{
|
||||||
|
auto file =
|
||||||
|
QDir::currentPath() + QDir::separator() + "data" + QDir::separator() + "hooks.d" + QDir::separator() + "1-test.json";
|
||||||
|
auto hook = ApplicationHook::loadFromFile(file);
|
||||||
|
EXPECT_TRUE(hook);
|
||||||
|
EXPECT_EQ(hook->hookName(), QString{"1-test.json"});
|
||||||
|
EXPECT_EQ(hook->execPath(), QString{"/usr/bin/echo"});
|
||||||
|
|
||||||
|
QStringList tmp{"for test"};
|
||||||
|
EXPECT_EQ(hook->args(), tmp);
|
||||||
|
|
||||||
|
tmp.push_front("/usr/bin/echo");
|
||||||
|
auto elem = generateHooks({std::move(hook).value()});
|
||||||
|
EXPECT_EQ(elem, tmp);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user