diff --git a/api/dbus/org.desktopspec.ApplicationManager1.Application.xml b/api/dbus/org.desktopspec.ApplicationManager1.Application.xml
index d6a91da..f5a48ed 100644
--- a/api/dbus/org.desktopspec.ApplicationManager1.Application.xml
+++ b/api/dbus/org.desktopspec.ApplicationManager1.Application.xml
@@ -11,6 +11,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dde-application-manager/src/main.cpp b/apps/dde-application-manager/src/main.cpp
index 88787f3..0e12ef8 100644
--- a/apps/dde-application-manager/src/main.cpp
+++ b/apps/dde-application-manager/src/main.cpp
@@ -21,7 +21,7 @@ void registerComplexDbusType()
qDBusRegisterMetaType();
qRegisterMetaType();
qDBusRegisterMetaType();
- qDBusRegisterMetaType>();
+ qDBusRegisterMetaType();
qRegisterMetaType();
qDBusRegisterMetaType();
qDBusRegisterMetaType();
diff --git a/apps/dde-application-manager/src/utils.cpp b/apps/dde-application-manager/src/utils.cpp
index c472dad..835dc85 100644
--- a/apps/dde-application-manager/src/utils.cpp
+++ b/apps/dde-application-manager/src/utils.cpp
@@ -9,9 +9,9 @@ bool registerObjectToDBus(QObject *o, const QString &path, const QString &interf
auto &con = ApplicationManager1DBus::instance().globalServerBus();
if (!con.registerObject(path, interface, o, QDBusConnection::RegisterOption::ExportAdaptors)) {
qCritical() << "register object failed:" << path << interface << con.lastError();
- } else {
- qDebug() << "register object:" << path << interface;
+ return false;
}
+ qDebug() << "register object:" << path << interface;
return true;
}
diff --git a/src/applicationmimeinfo.cpp b/src/applicationmimeinfo.cpp
new file mode 100644
index 0000000..80a65e0
--- /dev/null
+++ b/src/applicationmimeinfo.cpp
@@ -0,0 +1,363 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include "applicationmimeinfo.h"
+#include "global.h"
+
+constexpr decltype(auto) desktopSuffix = u8".desktop";
+
+QStringList getListFiles() noexcept
+{
+ QStringList files;
+ auto configDirs = getXDGConfigDirs();
+ auto dataDirs = getXDGDataDirs();
+ auto desktop = getCurrentDesktop().toLower();
+
+ auto appendListFile = [&files, &desktop](const QString &dir) {
+ QFileInfo cur{dir};
+ if (!cur.exists() or !cur.isDir()) {
+ return;
+ }
+
+ QDir tmp{cur.absoluteFilePath()};
+ auto desktopList = tmp.filePath(QString{"%1-mimeapps.list"}.arg(desktop));
+ if (QFileInfo::exists(desktopList)) {
+ files.append(desktopList);
+ }
+
+ desktopList = tmp.filePath("mimeapps.list");
+ if (QFileInfo::exists(desktopList)) {
+ files.append(desktopList);
+ }
+ };
+
+ std::for_each(configDirs.cbegin(), configDirs.cend(), appendListFile);
+ std::for_each(dataDirs.cbegin(), dataDirs.cend(), appendListFile);
+ std::reverse(files.begin(), files.end());
+ return files;
+}
+namespace {
+QString toString(const MimeContent &content) noexcept
+{
+ QString ret;
+
+ for (auto it = content.constKeyValueBegin(); it != content.constKeyValueEnd(); ++it) {
+ ret.append(QString{"[%1]\n"}.arg(it->first));
+ const auto &kvPairs = it->second;
+ for (auto inner = kvPairs.constKeyValueBegin(); inner != kvPairs.constKeyValueEnd(); ++inner) {
+ ret.append(QString{"%1="}.arg(inner->first));
+ for (const auto &app : inner->second) {
+ ret.append(QString{"%2;"}.arg(app));
+ }
+ ret.append('\n');
+ }
+ ret.append('\n');
+ }
+
+ return ret;
+}
+
+QString removeDesktopSuffix(const QString &str) noexcept
+{
+ return str.chopped(sizeof(desktopSuffix) - 1);
+}
+} // namespace
+
+std::optional MimeFileBase::loadFromFile(const QFileInfo &fileInfo, bool desktopSpec)
+{
+ bool isWritable{false};
+ auto filePath = fileInfo.absoluteFilePath();
+ if (filePath.startsWith(getXDGConfigHome()) or filePath.startsWith(getXDGDataHome())) {
+ isWritable = true;
+ }
+
+ QFile file{filePath};
+ if (!file.open(QFile::ReadOnly | QFile::Text | QFile::ExistingOnly)) {
+ qWarning() << "open" << filePath << "failed:" << file.errorString();
+ return std::nullopt;
+ }
+
+ QTextStream stream{&file};
+ stream.setEncoding(QStringConverter::Utf8);
+
+ MimeContent content;
+ MimeFileParser parser{stream, desktopSpec};
+ if (auto err = parser.parse(content); err != ParserError::NoError) {
+ qWarning() << "file:" << filePath << "parse failed:" << err;
+ return std::nullopt;
+ }
+
+ if (content.isEmpty()) {
+ qInfo() << "ignore empty file:" << filePath;
+ return std::nullopt;
+ }
+
+ return MimeFileBase{fileInfo, std::move(content), desktopSpec, isWritable};
+}
+
+MimeFileBase::MimeFileBase(const QFileInfo &info, MimeContent &&content, bool desktopSpec, bool writable)
+ : m_desktopSpec(desktopSpec)
+ , m_writable(writable)
+ , m_info(info)
+ , m_content(std::move(content))
+{
+}
+
+void MimeFileBase::reload() noexcept
+{
+ auto newBase = MimeFileBase::loadFromFile(fileInfo(), isDesktopSpecific());
+ if (!newBase) {
+ qWarning() << "reload" << fileInfo().absoluteFilePath() << "failed, content wouldn't be changed.";
+ return;
+ }
+
+ m_content = std::move(newBase->m_content);
+}
+
+MimeApps::MimeApps(MimeFileBase &&base)
+ : MimeFileBase(std::move(base))
+{
+}
+
+std::optional MimeApps::createMimeApps(const QString &filePath, bool desktopSpec) noexcept
+{
+ auto baseOpt = MimeFileBase::loadFromFile(QFileInfo{filePath}, desktopSpec);
+
+ if (!baseOpt) {
+ return std::nullopt;
+ }
+
+ return MimeApps{std::move(baseOpt).value()};
+}
+
+void MimeApps::insertToSection(const QString §ion, const QString &mimeType, const QString &appId) noexcept
+{
+ auto &map = content();
+
+ auto targetSection = map.find(section);
+ if (targetSection == map.end()) {
+ targetSection = map.insert(section, {});
+ }
+
+ QStringList newApps{appId + desktopSuffix};
+ auto oldApps = targetSection->find(mimeType);
+ if (oldApps != targetSection->end()) {
+ newApps.append(*oldApps);
+ }
+
+ targetSection->insert(mimeType, newApps);
+}
+
+void MimeApps::addAssociation(const QString &mimeType, const QString &appId) noexcept
+{
+ insertToSection(addedAssociations, mimeType, appId);
+}
+
+void MimeApps::removeAssociation(const QString &mimeType, const QString &appId) noexcept
+{
+ insertToSection(removedAssociations, mimeType, appId);
+}
+
+void MimeApps::setDefaultApplication(const QString &mimeType, const QString &appId) noexcept
+{
+ auto &map = content();
+
+ auto defaultSection = map.find(defaultApplications);
+ if (defaultSection == map.end()) {
+ defaultSection = map.insert(defaultApplications, {});
+ }
+
+ defaultSection->insert(mimeType, {appId + desktopSuffix});
+}
+
+void MimeApps::unsetDefaultApplication(const QString &mimeType) noexcept
+{
+ auto &map = content();
+
+ auto defaultSection = map.find(defaultApplications);
+ if (defaultSection == map.end()) {
+ return;
+ }
+
+ defaultSection->remove(mimeType);
+}
+
+AppList MimeApps::queryTypes(QString appId) const noexcept
+{
+ AppList ret;
+ appId.append(desktopSuffix);
+ const auto &lists = content();
+
+ if (const auto &adds = lists.constFind(addedAssociations); adds != lists.cend()) {
+ std::for_each(adds->constKeyValueBegin(), adds->constKeyValueEnd(), [&ret, &appId](const auto &it) {
+ if (it.second.contains(appId)) {
+ ret.added.append(it.first);
+ }
+ });
+ }
+
+ if (const auto &removes = lists.constFind(removedAssociations); removes != lists.cend()) {
+ std::for_each(removes->constKeyValueBegin(), removes->constKeyValueEnd(), [&ret, &appId](const auto &it) {
+ if (it.second.contains(appId)) {
+ ret.removed.removeOne(it.first);
+ }
+ });
+ }
+
+ return ret;
+}
+
+bool MimeApps::writeToFile() const noexcept
+{
+ auto filePath = fileInfo().absoluteFilePath();
+ if (!isWritable()) {
+ qDebug() << "shouldn't write file:" << filePath;
+ return false;
+ }
+
+ QFile file{filePath};
+
+ if (!file.open(QFile::ExistingOnly | QFile::Text | QFile::WriteOnly | QFile::Truncate)) {
+ qWarning() << "open" << filePath << "failed:" << file.errorString();
+ return false;
+ }
+
+ auto newContent = toString(content()).toLocal8Bit();
+ auto bytes = file.write(newContent);
+
+ if (bytes != newContent.size()) {
+ qWarning() << "incomplete content, write:" << bytes << "total:" << newContent.size();
+ return false;
+ }
+
+ return true;
+}
+
+QString MimeApps::queryDefaultApp(const QMimeType &type) const noexcept
+{
+ const auto &map = content();
+ auto defaultSection = map.find(defaultApplications);
+ if (defaultSection == map.end()) {
+ return {};
+ }
+
+ auto defaultApp = defaultSection->find(type.name());
+ if (defaultApp == defaultSection->end()) {
+ return {};
+ }
+
+ const auto &app = defaultApp.value();
+ if (app.isEmpty()) {
+ return {};
+ }
+
+ return removeDesktopSuffix(app.first());
+}
+
+QStringList MimeCache::queryTypes(QString appId) const noexcept
+{
+ QStringList ret;
+ appId.append(desktopSuffix);
+ const auto &cache = content()[mimeCache];
+ for (auto it = cache.constKeyValueBegin(); it != cache.constKeyValueEnd(); ++it) {
+ if (it->second.contains(appId)) {
+ ret.append(it->first);
+ }
+ }
+ return ret;
+}
+
+std::optional MimeCache::createMimeCache(const QString &filePath) noexcept
+{
+ auto baseOpt = MimeFileBase::loadFromFile(QFileInfo{filePath}, false);
+
+ if (!baseOpt) {
+ return std::nullopt;
+ }
+
+ return MimeCache{std::move(baseOpt).value()};
+}
+
+MimeCache::MimeCache(MimeFileBase &&base)
+ : MimeFileBase(std::move(base))
+{
+}
+
+QStringList MimeCache::queryApps(const QMimeType &type) const noexcept
+{
+ const auto &content = this->content();
+ auto it = content.constFind(mimeCache);
+ if (it == content.constEnd()) {
+ qDebug() << "this cache is broken, please reload.";
+ return {};
+ }
+
+ QStringList ret;
+ if (auto kv = it->constFind(type.name()); kv != it->constEnd()) {
+ const auto &apps = kv.value();
+ for (const auto &e : apps) {
+ if (!e.endsWith(desktopSuffix)) {
+ continue;
+ }
+ ret.append(removeDesktopSuffix(e));
+ }
+ }
+ return ret;
+}
+
+std::optional MimeInfo::createMimeInfo(const QString &directory) noexcept
+{
+ MimeInfo ret;
+ auto dirPath = QDir::cleanPath(directory);
+ QDir dir;
+
+ if (!QFileInfo::exists(dirPath)) {
+ qCritical() << "directory " << dirPath << "doesn't exists.";
+ return std::nullopt;
+ }
+ dir.setPath(dirPath);
+ ret.m_directory = dirPath;
+
+ QFileInfo cacheFile{dir.filePath("mimeinfo.cache")};
+ if (cacheFile.exists() and cacheFile.isFile()) {
+ ret.m_cache = MimeCache::createMimeCache(cacheFile.absoluteFilePath());
+ }
+
+ QFileInfo desktopAppList{dir.filePath(QString{"%1-mimeapps.list"}.arg(getCurrentDesktop().toLower()))};
+ if (desktopAppList.exists() and desktopAppList.isFile()) {
+ auto desktopApps = MimeApps::createMimeApps(desktopAppList.absoluteFilePath(), true);
+ if (desktopApps) {
+ ret.m_appsList.emplace_back(std::move(desktopApps).value());
+ }
+ }
+
+ QFileInfo appList{dir.filePath("mimeapps.list")};
+ if (auto userMimeApps = appList.absoluteFilePath();
+ userMimeApps.startsWith(getXDGConfigHome()) and (!appList.exists() or !appList.isFile())) [[unlikely]] {
+ QFile userFile{userMimeApps};
+ if (!userFile.open(QFile::WriteOnly | QFile::Text)) {
+ qCritical() << "failed to create user file:" << userMimeApps << userFile.errorString();
+ }
+ }
+
+ if (appList.exists() and appList.isFile()) {
+ auto apps = MimeApps::createMimeApps(appList.absoluteFilePath(), false);
+ if (apps) {
+ ret.m_appsList.emplace_back(std::move(apps).value());
+ }
+ }
+
+ return ret;
+}
+
+void MimeInfo::reload() noexcept
+{
+ for (auto &app : m_appsList) {
+ app.reload();
+ }
+
+ if (m_cache) {
+ m_cache->reload();
+ }
+}
diff --git a/src/applicationmimeinfo.h b/src/applicationmimeinfo.h
new file mode 100644
index 0000000..f95bdba
--- /dev/null
+++ b/src/applicationmimeinfo.h
@@ -0,0 +1,123 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef APPLICATIONMIMEINFO_H
+#define APPLICATIONMIMEINFO_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "mimefileparser.h"
+
+using MimeContent = MimeFileParser::Groups;
+
+QStringList getListFiles() noexcept;
+
+class MimeFileBase
+{
+public:
+ static std::optional loadFromFile(const QFileInfo &fileInfo, bool desktopSpec);
+ MimeFileBase(MimeFileBase &&) = default;
+ MimeFileBase &operator=(MimeFileBase &&) = default;
+ MimeFileBase(const MimeFileBase &) = delete;
+ MimeFileBase &operator=(const MimeFileBase &) = delete;
+
+ virtual ~MimeFileBase() = default;
+ [[nodiscard]] const QFileInfo &fileInfo() const noexcept { return m_info; }
+ [[nodiscard]] MimeContent &content() noexcept { return m_content; }
+ [[nodiscard]] const MimeContent &content() const noexcept { return m_content; }
+ [[nodiscard]] bool isDesktopSpecific() const noexcept { return m_desktopSpec; }
+ [[nodiscard]] bool isWritable() const noexcept { return m_writable; }
+
+ void reload() noexcept;
+
+private:
+ MimeFileBase(const QFileInfo &info, MimeContent &&content, bool desktopSpec, bool writable);
+ bool m_desktopSpec{false};
+ bool m_writable{false};
+ QFileInfo m_info;
+ MimeContent m_content;
+};
+
+struct AppList
+{
+ QStringList added;
+ QStringList removed;
+};
+
+class MimeApps : public MimeFileBase
+{
+public:
+ static std::optional createMimeApps(const QString &filePath, bool desktopSpec) noexcept;
+
+ MimeApps(MimeApps &&) = default;
+ MimeApps &operator=(MimeApps &&) = default;
+ MimeApps(const MimeApps &) = delete;
+ MimeApps &operator=(const MimeApps &) = delete;
+ ~MimeApps() override = default;
+
+ void addAssociation(const QString &mimeType, const QString &appId) noexcept;
+ void removeAssociation(const QString &mimeType, const QString &appId) noexcept;
+ void setDefaultApplication(const QString &mimeType, const QString &appId) noexcept;
+ void unsetDefaultApplication(const QString &mimeType) noexcept;
+
+ [[nodiscard]] QString queryDefaultApp(const QMimeType &type) const noexcept;
+ [[nodiscard]] AppList queryTypes(QString appId) const noexcept;
+ [[nodiscard]] bool writeToFile() const noexcept;
+
+private:
+ void insertToSection(const QString §ion, const QString &mimeType, const QString &appId) noexcept;
+ explicit MimeApps(MimeFileBase &&base);
+};
+
+class MimeCache : public MimeFileBase
+{
+public:
+ static std::optional createMimeCache(const QString &filePath) noexcept;
+
+ MimeCache(MimeCache &&) = default;
+ MimeCache &operator=(MimeCache &&) = default;
+ MimeCache(const MimeCache &) = delete;
+ MimeCache &operator=(const MimeCache &) = delete;
+ ~MimeCache() override = default;
+
+ [[nodiscard]] QStringList queryApps(const QMimeType &type) const noexcept;
+ [[nodiscard]] QStringList queryTypes(QString appId) const noexcept;
+
+private:
+ explicit MimeCache(MimeFileBase &&base);
+};
+
+class MimeInfo
+{
+public:
+ static std::optional createMimeInfo(const QString &directory) noexcept;
+
+ MimeInfo(MimeInfo &&) = default;
+ MimeInfo &operator=(MimeInfo &&) = default;
+ MimeInfo(const MimeInfo &) = delete;
+ MimeInfo &operator=(const MimeInfo &) = delete;
+ ~MimeInfo() = default;
+
+ [[nodiscard]] std::vector &appsList() noexcept { return m_appsList; }
+ [[nodiscard]] const std::vector &appsList() const noexcept { return m_appsList; }
+ [[nodiscard]] std::optional &cacheInfo() noexcept { return m_cache; }
+ [[nodiscard]] const std::optional &cacheInfo() const noexcept { return m_cache; }
+ [[nodiscard]] const QString &directory() const noexcept { return m_directory; }
+
+ void reload() noexcept;
+
+private:
+ MimeInfo() = default;
+ std::vector m_appsList;
+ std::optional m_cache{std::nullopt};
+ QString m_directory;
+};
+
+#endif
diff --git a/src/constant.h b/src/constant.h
index b510eb8..27f8321 100644
--- a/src/constant.h
+++ b/src/constant.h
@@ -17,7 +17,8 @@ constexpr auto DDEApplicationManager1ServiceName =
constexpr auto DDEApplicationManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1";
constexpr auto DDEAutoStartManager1ObjectPath = u8"/org/desktopspec/AutoStartManager1";
-constexpr auto DDEApplicationManager1JobManagerObjectPath = u8"/org/desktopspec/ApplicationManager1/JobManager1";
+constexpr auto DDEApplicationManager1JobManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/JobManager1";
+constexpr auto DDEApplicationManager1MimeManager1ObjectPath = u8"/org/desktopspec/ApplicationManager1/MimeManager1";
constexpr auto DesktopFileEntryKey = u8"Desktop Entry";
constexpr auto DesktopFileActionKey = u8"Desktop Action ";
@@ -36,12 +37,13 @@ constexpr auto ApplicationManagerDestDBusName =
#endif
constexpr auto ObjectManagerInterface = "org.desktopspec.DBus.ObjectManager";
-constexpr auto JobManagerInterface = "org.desktopspec.JobManager1";
+constexpr auto JobManager1Interface = "org.desktopspec.JobManager1";
constexpr auto JobInterface = "org.desktopspec.JobManager1.Job";
-constexpr auto ApplicationManagerInterface = "org.desktopspec.ApplicationManager1";
+constexpr auto ApplicationManager1Interface = "org.desktopspec.ApplicationManager1";
constexpr auto InstanceInterface = "org.desktopspec.ApplicationManager1.Instance";
constexpr auto ApplicationInterface = "org.desktopspec.ApplicationManager1.Application";
constexpr auto PropertiesInterface = u8"org.freedesktop.DBus.Properties";
+constexpr auto MimeManager1Interface = u8"org.desktopspec.MimeManager1";
constexpr auto systemdOption = u8"systemd";
constexpr auto splitOption = u8"split";
diff --git a/src/dbus/CMakeLists.txt b/src/dbus/CMakeLists.txt
index afbfd82..92ffdd6 100644
--- a/src/dbus/CMakeLists.txt
+++ b/src/dbus/CMakeLists.txt
@@ -13,6 +13,7 @@ qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.deskto
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.JobManager1.Job.xml dbus/jobservice.h JobService)
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.ObjectManager1.xml dbus/applicationmanager1service.h ApplicationManager1Service AMobjectmanager1adaptor AMObjectManagerAdaptor)
qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.ObjectManager1.xml dbus/applicationservice.h ApplicationService APPobjectmanager1adaptor APPObjectManagerAdaptor)
+qt_add_dbus_adaptor(dde_am_dbus_SOURCE ${PROJECT_SOURCE_DIR}/api/dbus/org.desktopspec.MimeManager1.xml dbus/mimemanager1service.h MimeManager1Service)
target_sources(dde_am_dbus PRIVATE
${dde_am_dbus_SOURCE}
diff --git a/src/dbus/applicationmanager1service.cpp b/src/dbus/applicationmanager1service.cpp
index f68731b..32d2364 100644
--- a/src/dbus/applicationmanager1service.cpp
+++ b/src/dbus/applicationmanager1service.cpp
@@ -37,17 +37,20 @@ void ApplicationManager1Service::initService(QDBusConnection &connection) noexce
std::terminate();
}
- if (!registerObjectToDBus(this, DDEApplicationManager1ObjectPath, ApplicationManagerInterface)) {
+ if (!registerObjectToDBus(this, DDEApplicationManager1ObjectPath, ApplicationManager1Interface)) {
std::terminate();
}
- m_jobManager.reset(new (std::nothrow) JobManager1Service(this));
-
- if (!m_jobManager) {
+ if (m_jobManager.reset(new (std::nothrow) JobManager1Service(this)); !m_jobManager) {
qCritical() << "new JobManager failed.";
std::terminate();
}
+ if (m_mimeManager.reset(new (std::nothrow) MimeManager1Service(this)); !m_mimeManager) {
+ qCritical() << "new MimeManager failed.";
+ std::terminate();
+ }
+
auto &dispatcher = SystemdSignalDispatcher::instance();
connect(&dispatcher, &SystemdSignalDispatcher::SystemdUnitNew, this, &ApplicationManager1Service::addInstanceToApplication);
@@ -67,6 +70,8 @@ void ApplicationManager1Service::initService(QDBusConnection &connection) noexce
qFatal("connect to ApplicationUpdated failed.");
}
+ scanMimeInfos();
+
scanApplications();
scanInstances();
@@ -208,6 +213,20 @@ void ApplicationManager1Service::removeInstanceFromApplication(const QString &un
});
}
+void ApplicationManager1Service::scanMimeInfos() noexcept
+{
+ QStringList dirs;
+ dirs.append(getXDGConfigDirs());
+ dirs.append(getDesktopFileDirs());
+
+ for (const auto &dir : dirs) {
+ auto info = MimeInfo::createMimeInfo(dir);
+ if (info) {
+ m_mimeManager->appendMimeInfo(std::move(info).value());
+ }
+ }
+}
+
void ApplicationManager1Service::scanApplications() noexcept
{
const auto &desktopFileDirs = getDesktopFileDirs();
@@ -215,7 +234,7 @@ void ApplicationManager1Service::scanApplications() noexcept
applyIteratively(
QList(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
[this](const QFileInfo &info) -> bool {
- DesktopErrorCode err{DesktopErrorCode::NoError};
+ ParserError err{ParserError::NoError};
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
if (!ret.has_value()) {
qWarning() << "failed to search File:" << err;
@@ -449,7 +468,7 @@ void ApplicationManager1Service::updateApplication(const QSharedPointerparse(desktopFile);
- if (err != DesktopErrorCode::NoError) {
+ if (err != ParserError::NoError) {
qWarning() << "update desktop file failed:" << err << ", content wouldn't change.";
return;
}
@@ -475,7 +494,7 @@ void ApplicationManager1Service::ReloadApplications()
applyIteratively(
QList(desktopFileDirs.cbegin(), desktopFileDirs.cend()),
[this, &apps](const QFileInfo &info) -> bool {
- DesktopErrorCode err{DesktopErrorCode::NoError};
+ ParserError err{ParserError::NoError};
auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err);
if (!ret.has_value()) {
return false;
@@ -488,7 +507,7 @@ void ApplicationManager1Service::ReloadApplications()
m_applicationList.cend(),
[&file](const QSharedPointer &app) { return file.desktopId() == app->id(); });
- if (err != DesktopErrorCode::NoError) {
+ if (err != ParserError::NoError) {
qWarning() << "error occurred:" << err << " skip this application.";
return false;
}
@@ -515,3 +534,17 @@ ObjectMap ApplicationManager1Service::GetManagedObjects() const
{
return dumpDBusObject(m_applicationList);
}
+
+QMap>
+ApplicationManager1Service::findApplicationsByIds(const QStringList &appIds) const noexcept
+{
+ QMap> ret;
+ for (auto it = m_applicationList.constKeyValueBegin(); it != m_applicationList.constKeyValueEnd(); ++it) {
+ const auto &ptr = it->second;
+ if (appIds.contains(ptr->id())) {
+ ret.insert(it->first, it->second);
+ }
+ }
+
+ return ret;
+}
diff --git a/src/dbus/applicationmanager1service.h b/src/dbus/applicationmanager1service.h
index 11cd57f..5445d49 100644
--- a/src/dbus/applicationmanager1service.h
+++ b/src/dbus/applicationmanager1service.h
@@ -14,6 +14,7 @@
#include
#include "applicationmanagerstorage.h"
#include "dbus/jobmanager1service.h"
+#include "dbus/mimemanager1service.h"
#include "desktopentry.h"
#include "identifier.h"
@@ -38,12 +39,15 @@ public:
bool addApplication(DesktopFile desktopFileSource) noexcept;
void removeOneApplication(const QDBusObjectPath &application) noexcept;
void removeAllApplication() noexcept;
-
+ [[nodiscard]] QMap>
+ findApplicationsByIds(const QStringList &appIds) const noexcept;
void updateApplication(const QSharedPointer &destApp, DesktopFile desktopFile) noexcept;
[[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; }
+ [[nodiscard]] MimeManager1Service &mimeManager() noexcept { return *m_mimeManager; }
+ [[nodiscard]] const MimeManager1Service &mimeManager() const noexcept { return *m_mimeManager; }
public Q_SLOTS:
QString Identify(const QDBusUnixFileDescriptor &pidfd,
@@ -60,10 +64,12 @@ Q_SIGNALS:
private:
std::unique_ptr m_identifier;
std::weak_ptr m_storage;
+ QScopedPointer m_mimeManager{nullptr};
QScopedPointer m_jobManager{nullptr};
QStringList m_hookElements;
QMap> m_applicationList;
+ void scanMimeInfos() noexcept;
void scanApplications() noexcept;
void scanInstances() noexcept;
void scanAutoStart() noexcept;
diff --git a/src/dbus/applicationservice.cpp b/src/dbus/applicationservice.cpp
index 4aa7090..f242c5d 100644
--- a/src/dbus/applicationservice.cpp
+++ b/src/dbus/applicationservice.cpp
@@ -5,7 +5,6 @@
#include "dbus/applicationservice.h"
#include "APPobjectmanager1adaptor.h"
#include "applicationchecker.h"
-#include "applicationmanager1service.h"
#include "applicationmanagerstorage.h"
#include "propertiesForwarder.h"
#include "dbus/instanceadaptor.h"
@@ -83,7 +82,7 @@ QSharedPointer ApplicationService::createApplicationService(
std::unique_ptr entry{std::make_unique()};
auto error = entry->parse(sourceStream);
- if (error != DesktopErrorCode::NoError) {
+ if (error != ParserError::NoError) {
qWarning() << "parse failed:" << error << app->desktopFileSource().sourcePath();
return nullptr;
}
@@ -486,6 +485,79 @@ void ApplicationService::setAutoStart(bool autostart) noexcept
emit autostartChanged();
}
+QStringList ApplicationService::mimeTypes() const noexcept
+{
+ QStringList ret;
+ const auto &desktopFilePath = m_desktopSource.sourcePath();
+ const auto &cacheList = parent()->mimeManager().infos();
+ auto cache = std::find_if(cacheList.cbegin(), cacheList.cend(), [&desktopFilePath](const MimeInfo &info) {
+ return desktopFilePath.startsWith(info.directory());
+ });
+
+ const auto &info = cache->cacheInfo();
+ if (info) {
+ ret.append(info->queryTypes(id()));
+ }
+
+ AppList tmp;
+
+ for (auto it = cacheList.crbegin(); it != cacheList.crend(); ++it) {
+ const auto &list = it->appsList();
+ std::for_each(list.crbegin(), list.crend(), [&tmp, this](const MimeApps &app) {
+ auto [added, removed] = app.queryTypes(id());
+ tmp.added.append(std::move(added));
+ tmp.removed.append(std::move(removed));
+ });
+ };
+
+ tmp.added.removeDuplicates();
+ tmp.removed.removeDuplicates();
+ for (const auto &it : tmp.removed) {
+ tmp.added.removeOne(it);
+ }
+
+ ret.append(std::move(tmp.added));
+ ret.removeDuplicates();
+ return ret;
+}
+
+void ApplicationService::setMimeTypes(const QStringList &value) noexcept
+{
+ auto oldMimes = mimeTypes();
+ auto newMimes = value;
+ std::sort(oldMimes.begin(), oldMimes.end());
+ std::sort(newMimes.begin(), newMimes.end());
+
+ QStringList newAdds;
+ QStringList newRemoved;
+
+ std::set_difference(oldMimes.begin(), oldMimes.end(), newMimes.begin(), newMimes.end(), std::back_inserter(newRemoved));
+ std::set_difference(newMimes.begin(), newMimes.end(), oldMimes.begin(), oldMimes.end(), std::back_inserter(newAdds));
+
+ static QString userDir = getXDGConfigHome();
+ auto &infos = parent()->mimeManager().infos();
+ auto userInfo = std::find_if(infos.begin(), infos.end(), [](const MimeInfo &info) { return info.directory() == userDir; });
+ if (userInfo == infos.cend()) {
+ sendErrorReply(QDBusError::Failed, "user-specific config file doesn't exists.");
+ return;
+ }
+
+ const auto &list = userInfo->appsList().rbegin();
+ const auto &appId = id();
+ for (const auto &add : newAdds) {
+ list->addAssociation(add, appId);
+ }
+ for (const auto &remove : newRemoved) {
+ list->removeAssociation(remove, appId);
+ }
+
+ if (!list->writeToFile()) {
+ qWarning() << "error occurred when write mime association to file";
+ }
+
+ emit MimeTypesChanged();
+}
+
QList ApplicationService::instances() const noexcept
{
return m_Instances.keys();
@@ -567,6 +639,7 @@ void ApplicationService::resetEntry(DesktopEntry *newEntry) noexcept
emit actionNameChanged();
emit actionsChanged();
emit categoriesChanged();
+ emit MimeTypesChanged();
}
LaunchTask ApplicationService::unescapeExec(const QString &str, const QStringList &fields)
diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h
index 986d6a1..281d94c 100644
--- a/src/dbus/applicationservice.h
+++ b/src/dbus/applicationservice.h
@@ -23,6 +23,7 @@
#include "global.h"
#include "desktopentry.h"
#include "dbus/jobmanager1service.h"
+#include "applicationmanager1service.h"
QString getDeepinWineScaleFactor(const QString &appId) noexcept;
@@ -67,6 +68,10 @@ public:
[[nodiscard]] bool isAutoStart() const noexcept;
void setAutoStart(bool autostart) noexcept;
+ Q_PROPERTY(QStringList MimeTypes READ mimeTypes WRITE setMimeTypes)
+ [[nodiscard]] QStringList mimeTypes() const noexcept;
+ void setMimeTypes(const QStringList &value) noexcept;
+
Q_PROPERTY(QList Instances READ instances NOTIFY instanceChanged)
[[nodiscard]] QList instances() const noexcept;
@@ -129,6 +134,7 @@ Q_SIGNALS:
void actionNameChanged();
void actionsChanged();
void categoriesChanged();
+ void MimeTypesChanged();
private:
friend class ApplicationManager1Service;
diff --git a/src/dbus/jobmanager1service.cpp b/src/dbus/jobmanager1service.cpp
index 62ba2a9..d471fba 100644
--- a/src/dbus/jobmanager1service.cpp
+++ b/src/dbus/jobmanager1service.cpp
@@ -9,7 +9,7 @@ JobManager1Service::JobManager1Service(ApplicationManager1Service *parent)
: m_parent(parent)
{
new JobManager1Adaptor{this};
- if (!registerObjectToDBus(this, DDEApplicationManager1JobManagerObjectPath, JobManagerInterface)) {
+ if (!registerObjectToDBus(this, DDEApplicationManager1JobManager1ObjectPath, JobManager1Interface)) {
std::terminate();
}
qRegisterMetaType();
diff --git a/src/dbus/jobmanager1service.h b/src/dbus/jobmanager1service.h
index 4d9dfe4..ff559bf 100644
--- a/src/dbus/jobmanager1service.h
+++ b/src/dbus/jobmanager1service.h
@@ -51,7 +51,7 @@ public:
static_assert(std::is_invocable_v, "param type must be QVariant.");
QString objectPath =
- QString{"%1/%2"}.arg(DDEApplicationManager1JobManagerObjectPath).arg(QUuid::createUuid().toString(QUuid::Id128));
+ QString{"%1/%2"}.arg(DDEApplicationManager1JobManager1ObjectPath).arg(QUuid::createUuid().toString(QUuid::Id128));
QFuture future = QtConcurrent::mappedReduced(std::move(args),
func,
qOverload(&QVariantList::append),
diff --git a/src/dbus/mimemanager1service.cpp b/src/dbus/mimemanager1service.cpp
new file mode 100644
index 0000000..92f9bac
--- /dev/null
+++ b/src/dbus/mimemanager1service.cpp
@@ -0,0 +1,114 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include "dbus/mimemanager1adaptor.h"
+#include "applicationmanager1service.h"
+#include "applicationservice.h"
+#include "constant.h"
+
+MimeManager1Service::MimeManager1Service(ApplicationManager1Service *parent)
+ : QObject(parent)
+{
+ new MimeManager1Adaptor{this};
+ if (!registerObjectToDBus(this, DDEApplicationManager1MimeManager1ObjectPath, MimeManager1Interface)) {
+ std::terminate();
+ }
+}
+
+MimeManager1Service::~MimeManager1Service() = default;
+
+ObjectMap MimeManager1Service::listApplications(const QString &mimeType) const noexcept
+{
+ auto mime = m_database.mimeTypeForName(mimeType);
+ if (!mime.isValid()) {
+ qWarning() << "can't find" << mimeType;
+ return {};
+ }
+
+ QStringList appIds;
+
+ for (auto it = m_infos.rbegin(); it != m_infos.rend(); ++it) {
+ const auto &info = it->cacheInfo();
+ if (!info) {
+ continue;
+ }
+ auto apps = info->queryApps(mime);
+ appIds.append(std::move(apps));
+ }
+ appIds.removeDuplicates();
+ qInfo() << "query" << mimeType << "find:" << appIds;
+ const auto &apps = static_cast(parent())->findApplicationsByIds(appIds);
+ return dumpDBusObject(apps);
+}
+
+QString MimeManager1Service::queryFileTypeAndDefaultApplication(const QString &filePath,
+ QDBusObjectPath &application) const noexcept
+{
+ QString mimeType;
+ application = QDBusObjectPath{"/"};
+
+ auto mime = m_database.mimeTypeForFile(filePath);
+ if (mime.isValid()) {
+ mimeType = mime.name();
+ }
+
+ QString defaultAppId;
+ for (auto it1 = m_infos.rbegin(); it1 != m_infos.rend(); ++it1) {
+ const auto &list = it1->appsList();
+ for (auto it2 = list.rbegin(); it2 != list.rend(); ++it2) {
+ if (auto app = it2->queryDefaultApp(mime); !app.isEmpty()) {
+ defaultAppId = app;
+ }
+ }
+ }
+
+ if (defaultAppId.isEmpty()) {
+ qInfo() << "file's mimeType:" << mime.name() << "but can't find a default application for this type.";
+ return mimeType;
+ }
+
+ const auto &apps = static_cast(parent())->findApplicationsByIds({defaultAppId});
+ if (apps.isEmpty()) {
+ qWarning() << "default application has been found:" << defaultAppId
+ << " but we can't find corresponding application in ApplicationManagerService.";
+ } else {
+ application = apps.firstKey();
+ }
+
+ return mimeType;
+}
+
+void MimeManager1Service::setDefaultApplication(const KVPairs &defaultApps) noexcept
+{
+ auto &app = m_infos.front().appsList();
+ auto userConfig = std::find_if(
+ app.begin(), app.end(), [](const MimeApps &config) { return !config.isDesktopSpecific(); }); // always find this
+
+ for (auto it = defaultApps.constKeyValueBegin(); it != defaultApps.constKeyValueEnd(); ++it) {
+ userConfig->setDefaultApplication(it->first, it->second);
+ }
+
+ if (!userConfig->writeToFile()) {
+ sendErrorReply(QDBusError::Failed, "set default app failed, these config will be reset after re-login.");
+ }
+}
+
+void MimeManager1Service::unsetDefaultApplication(const QStringList &mimeTypes) noexcept
+{
+ auto &app = m_infos.front().appsList();
+ auto userConfig = std::find_if(app.begin(), app.end(), [](const MimeApps &config) { return !config.isDesktopSpecific(); });
+
+ for (const auto &mime : mimeTypes) {
+ userConfig->unsetDefaultApplication(mime);
+ }
+
+ if (!userConfig->writeToFile()) {
+ sendErrorReply(QDBusError::Failed, "unset default app failed, these config will be reset after re-login.");
+ }
+}
+
+void MimeManager1Service::appendMimeInfo(MimeInfo &&info)
+{
+ m_infos.emplace_back(std::move(info));
+}
diff --git a/src/dbus/mimemanager1service.h b/src/dbus/mimemanager1service.h
new file mode 100644
index 0000000..8773743
--- /dev/null
+++ b/src/dbus/mimemanager1service.h
@@ -0,0 +1,39 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef MIMEMANAGERSERVICE_H
+#define MIMEMANAGERSERVICE_H
+
+#include
+#include
+#include
+#include "global.h"
+#include "applicationmimeinfo.h"
+
+class ApplicationManager1Service;
+
+class MimeManager1Service : public QObject, public QDBusContext
+{
+ Q_OBJECT
+public:
+ explicit MimeManager1Service(ApplicationManager1Service *parent);
+ ~MimeManager1Service() override;
+
+ void appendMimeInfo(MimeInfo &&info);
+ [[nodiscard]] const auto &infos() const noexcept { return m_infos; }
+ [[nodiscard]] auto &infos() noexcept { return m_infos; }
+
+public Q_SLOTS:
+ [[nodiscard]] ObjectMap listApplications(const QString &mimeType) const noexcept;
+ [[nodiscard]] QString queryFileTypeAndDefaultApplication(const QString &filePath,
+ QDBusObjectPath &application) const noexcept;
+ void setDefaultApplication(const KVPairs &defaultApps) noexcept;
+ void unsetDefaultApplication(const QStringList &mimeTypes) noexcept;
+
+private:
+ QMimeDatabase m_database;
+ std::vector m_infos;
+};
+
+#endif
diff --git a/src/desktopentry.cpp b/src/desktopentry.cpp
index 7874b1f..f76146e 100644
--- a/src/desktopentry.cpp
+++ b/src/desktopentry.cpp
@@ -4,6 +4,7 @@
#include "desktopentry.h"
#include "global.h"
+#include "desktopfileparser.h"
#include
#include
#include
@@ -15,203 +16,6 @@
#include
#include
-namespace {
-bool isInvalidLocaleString(const QString &str) noexcept
-{
- constexpr auto Language = R"((?:[a-z]+))"; // language of locale postfix. eg.(en, zh)
- constexpr auto Country = R"((?:_[A-Z]+))"; // country of locale postfix. eg.(US, CN)
- constexpr auto Encoding = R"((?:\.[0-9A-Z-]+))"; // encoding of locale postfix. eg.(UFT-8)
- constexpr auto Modifier = R"((?:@[a-zA-Z=;]+))"; // modifier of locale postfix. eg.(euro;collation=traditional)
- const static auto validKey = QString(R"(^%1%2?%3?%4?$)").arg(Language, Country, Encoding, Modifier);
- // example: https://regex101.com/r/hylOay/2
- static const QRegularExpression _re = []() -> QRegularExpression {
- QRegularExpression tmp{validKey};
- tmp.optimize();
- return tmp;
- }();
- thread_local const auto re = _re;
-
- return re.match(str).hasMatch();
-}
-
-bool hasNonAsciiAndControlCharacters(const QString &str) noexcept
-{
- static const QRegularExpression _matchControlChars = []() {
- QRegularExpression tmp{R"(\p{Cc})"};
- tmp.optimize();
- return tmp;
- }();
- thread_local const auto matchControlChars = _matchControlChars;
- static const QRegularExpression _matchNonAsciiChars = []() {
- QRegularExpression tmp{R"([^\x00-\x7f])"};
- tmp.optimize();
- return tmp;
- }();
- thread_local const auto matchNonAsciiChars = _matchNonAsciiChars;
- if (str.contains(matchControlChars) and str.contains(matchNonAsciiChars)) {
- return true;
- }
-
- return false;
-}
-
-struct Parser
-{
- Parser(QTextStream &stream)
- : m_stream(stream){};
- QTextStream &m_stream;
- QString m_line;
-
- using Groups = QMap>;
-
- DesktopErrorCode parse(Groups &groups) noexcept;
-
-private:
- void skip() noexcept;
- DesktopErrorCode addGroup(Groups &groups) noexcept;
- DesktopErrorCode addEntry(Groups::iterator &group) noexcept;
-};
-
-void Parser::skip() noexcept
-{
- while (!m_stream.atEnd() and (m_line.startsWith('#') or m_line.isEmpty())) {
- m_line = m_stream.readLine().trimmed();
- }
-}
-
-DesktopErrorCode Parser::parse(Groups &ret) noexcept
-{
- std::remove_reference_t groups;
- while (!m_stream.atEnd()) {
- auto err = addGroup(groups);
- if (err != DesktopErrorCode::NoError) {
- return err;
- }
-
- if (groups.size() != 1) {
- continue;
- }
-
- if (groups.keys().first() != DesktopFileEntryKey) {
- qWarning() << "There should be nothing preceding "
- "'Desktop Entry' group in the desktop entry file "
- "but possibly one or more comments.";
- return DesktopErrorCode::InvalidFormat;
- }
- }
-
- if (!m_line.isEmpty()) {
- qCritical() << "Something is wrong in Desktop file parser, check logic.";
- return DesktopErrorCode::InternalError;
- }
-
- ret = std::move(groups);
- return DesktopErrorCode::NoError;
-}
-
-DesktopErrorCode Parser::addGroup(Groups &ret) noexcept
-{
- skip();
- if (!m_line.startsWith('[')) {
- qWarning() << "Invalid desktop file format: unexpected line:" << m_line;
- return DesktopErrorCode::InvalidFormat;
- }
-
- // Parsing group header.
- // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#group-header
-
- auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
-
- if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
- qWarning() << "group header invalid:" << m_line;
- return DesktopErrorCode::InvalidFormat;
- }
-
- if (ret.find(groupHeader) != ret.end()) {
- qWarning() << "duplicated group header detected:" << groupHeader;
- return DesktopErrorCode::InvalidFormat;
- }
-
- auto group = ret.insert(groupHeader, {});
-
- m_line.clear();
- while (!m_stream.atEnd() && !m_line.startsWith('[')) {
- skip();
- if (m_line.startsWith('[')) {
- break;
- }
- auto err = addEntry(group);
- if (err != DesktopErrorCode::NoError) {
- return err;
- }
- }
- return DesktopErrorCode::NoError;
-}
-
-DesktopErrorCode Parser::addEntry(Groups::iterator &group) noexcept
-{
- auto line = m_line;
- m_line.clear();
- auto splitCharIndex = line.indexOf('=');
- if (splitCharIndex == -1) {
- qWarning() << "invalid line in desktop file, skip it:" << line;
- return DesktopErrorCode::NoError;
- }
- auto keyStr = line.first(splitCharIndex).trimmed();
- auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
-
- QString key{""};
- QString localeStr{defaultKeyStr};
- // NOTE:
- // We are process "localized keys" here, for usage check:
- // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
- qsizetype localeBegin = keyStr.indexOf('[');
- qsizetype localeEnd = keyStr.lastIndexOf(']');
- if ((localeBegin == -1 && localeEnd != -1) || (localeBegin != -1 && localeEnd == -1)) {
- qWarning() << "unmatched [] detected in desktop file, skip this line: " << line;
- return DesktopErrorCode::NoError;
- }
-
- if (localeBegin == -1 && localeEnd == -1) {
- key = keyStr;
- } else {
- key = keyStr.sliced(0, localeBegin);
- localeStr = keyStr.sliced(localeBegin + 1, localeEnd - localeBegin - 1); // strip '[' and ']'
- }
-
- static const QRegularExpression _re = []() {
- QRegularExpression tmp{"R([^A-Za-z0-9-])"};
- tmp.optimize();
- return tmp;
- }();
- // NOTE: https://stackoverflow.com/a/25583104
- thread_local const QRegularExpression re = _re;
- if (re.match(key).hasMatch()) {
- qWarning() << "invalid key name, skip this line:" << line;
- return DesktopErrorCode::NoError;
- }
-
- if (localeStr != defaultKeyStr && !isInvalidLocaleString(localeStr)) {
- qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
- }
-
- auto keyIt = group->find(key);
- if (keyIt != group->end() && keyIt->find(localeStr) != keyIt->end()) {
- qWarning() << "duplicated localestring, skip this line:" << line;
- return DesktopErrorCode::NoError;
- }
-
- if (keyIt == group->end()) {
- group->insert(key, {{localeStr, valueStr}});
- return DesktopErrorCode::NoError;
- }
-
- keyIt->insert(localeStr, valueStr);
- return DesktopErrorCode::NoError;
-}
-
-} // namespace
-
QString DesktopFile::sourcePath() const noexcept
{
if (!m_fileSource) {
@@ -293,20 +97,20 @@ std::optional DesktopFile::createTemporaryDesktopFile(const QString
return createTemporaryDesktopFile(std::move(tempFile));
}
-std::optional DesktopFile::searchDesktopFileByPath(const QString &desktopFile, DesktopErrorCode &err) noexcept
+std::optional DesktopFile::searchDesktopFileByPath(const QString &desktopFile, ParserError &err) noexcept
{
decltype(auto) desktopSuffix = ".desktop";
if (!desktopFile.endsWith(desktopSuffix)) {
qWarning() << "file isn't a desktop file:" << desktopFile;
- err = DesktopErrorCode::MismatchedFile;
+ err = ParserError::MismatchedFile;
return std::nullopt;
}
QFileInfo fileinfo{desktopFile};
if (!fileinfo.isAbsolute() or !fileinfo.exists()) {
qWarning() << "desktop file not found.";
- err = DesktopErrorCode::NotFound;
+ err = ParserError::NotFound;
return std::nullopt;
}
@@ -334,11 +138,11 @@ std::optional DesktopFile::searchDesktopFileByPath(const QString &d
auto [ctime, mtime, _] = getFileTimeInfo(QFileInfo{*filePtr});
- err = DesktopErrorCode::NoError;
+ err = ParserError::NoError;
return DesktopFile{std::move(filePtr), std::move(id), mtime, ctime};
}
-std::optional DesktopFile::searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept
+std::optional DesktopFile::searchDesktopFileById(const QString &appId, ParserError &err) noexcept
{
auto XDGDataDirs = getDesktopFileDirs();
constexpr auto desktopSuffix = u8".desktop";
@@ -360,7 +164,7 @@ std::optional DesktopFile::searchDesktopFileById(const QString &app
}
}
- err = DesktopErrorCode::NotFound;
+ err = ParserError::NotFound;
return std::nullopt;
}
@@ -369,13 +173,13 @@ bool DesktopFile::modified(qint64 time) const noexcept
return time != m_mtime;
}
-DesktopErrorCode DesktopEntry::parse(const DesktopFile &file) noexcept
+ParserError DesktopEntry::parse(const DesktopFile &file) noexcept
{
DesktopFileGuard guard{file};
if (!guard.try_open()) {
qWarning() << file.sourcePath() << "can't open.";
- return DesktopErrorCode::OpenFailed;
+ return ParserError::OpenFailed;
}
QTextStream stream;
@@ -383,29 +187,29 @@ DesktopErrorCode DesktopEntry::parse(const DesktopFile &file) noexcept
return parse(stream);
}
-DesktopErrorCode DesktopEntry::parse(QTextStream &stream) noexcept
+ParserError DesktopEntry::parse(QTextStream &stream) noexcept
{
- if (m_parsed == true) {
- return DesktopErrorCode::Parsed;
+ if (m_parsed) {
+ return ParserError::Parsed;
}
if (stream.atEnd()) {
- return DesktopErrorCode::OpenFailed;
+ return ParserError::OpenFailed;
}
stream.setEncoding(QStringConverter::Utf8);
- DesktopErrorCode err{DesktopErrorCode::NoError};
- Parser p(stream);
+ ParserError err{ParserError::NoError};
+ DesktopFileParser p(stream);
err = p.parse(m_entryMap);
m_parsed = true;
- if (err != DesktopErrorCode::NoError) {
+ if (err != ParserError::NoError) {
return err;
}
if (!checkMainEntryValidation()) {
qWarning() << "invalid MainEntry, abort.";
- err = DesktopErrorCode::MissingInfo;
+ err = ParserError::MissingInfo;
}
return err;
@@ -584,40 +388,3 @@ QDebug operator<<(QDebug debug, const DesktopEntry::Value &v)
debug << static_cast &>(v);
return debug;
}
-
-QDebug operator<<(QDebug debug, const DesktopErrorCode &v)
-{
- QDebugStateSaver saver{debug};
- QString errMsg;
- switch (v) {
- case DesktopErrorCode::NoError: {
- errMsg = "no error.";
- } break;
- case DesktopErrorCode::NotFound: {
- errMsg = "file not found.";
- } break;
- case DesktopErrorCode::MismatchedFile: {
- errMsg = "file type is mismatched.";
- } break;
- case DesktopErrorCode::InvalidLocation: {
- errMsg = "file location is invalid, please check $XDG_DATA_DIRS.";
- } break;
- case DesktopErrorCode::OpenFailed: {
- errMsg = "couldn't open the file.";
- } break;
- case DesktopErrorCode::InvalidFormat: {
- errMsg = "the format of desktopEntry file is invalid.";
- } break;
- case DesktopErrorCode::MissingInfo: {
- errMsg = "missing required infomation.";
- } break;
- case DesktopErrorCode::Parsed: {
- errMsg = "this desktop entry is parsed.";
- } break;
- case DesktopErrorCode::InternalError: {
- errMsg = "internal error of parser.";
- } break;
- }
- debug << errMsg;
- return debug;
-}
diff --git a/src/desktopentry.h b/src/desktopentry.h
index 0bf85f5..789a356 100644
--- a/src/desktopentry.h
+++ b/src/desktopentry.h
@@ -12,21 +12,10 @@
#include
#include
#include
+#include "iniParser.h"
constexpr static auto defaultKeyStr = "default";
-enum class DesktopErrorCode {
- NoError,
- NotFound,
- MismatchedFile,
- InvalidLocation,
- InvalidFormat,
- OpenFailed,
- MissingInfo,
- Parsed,
- InternalError,
-};
-
enum class EntryContext { Unknown, EntryOuter, Entry, Done };
enum class EntryValueType { String, LocaleString, Boolean, IconString };
@@ -53,8 +42,8 @@ struct DesktopFile
friend bool operator==(const DesktopFile &lhs, const DesktopFile &rhs);
friend bool operator!=(const DesktopFile &lhs, const DesktopFile &rhs);
- static std::optional searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept;
- static std::optional searchDesktopFileByPath(const QString &desktopFilePath, DesktopErrorCode &err) noexcept;
+ static std::optional searchDesktopFileById(const QString &appId, ParserError &err) noexcept;
+ static std::optional searchDesktopFileByPath(const QString &desktopFilePath, ParserError &err) noexcept;
static std::optional createTemporaryDesktopFile(const QString &temporaryFile) noexcept;
static std::optional createTemporaryDesktopFile(std::unique_ptr temporaryFile) noexcept;
@@ -138,8 +127,8 @@ public:
DesktopEntry &operator=(DesktopEntry &&) = default;
~DesktopEntry() = default;
- [[nodiscard]] DesktopErrorCode parse(const DesktopFile &file) noexcept;
- [[nodiscard]] DesktopErrorCode parse(QTextStream &stream) noexcept;
+ [[nodiscard]] ParserError parse(const DesktopFile &file) noexcept;
+ [[nodiscard]] ParserError parse(QTextStream &stream) noexcept;
[[nodiscard]] std::optional> group(const QString &key) const noexcept;
[[nodiscard]] std::optional value(const QString &key, const QString &valueKey) const noexcept;
@@ -154,8 +143,6 @@ private:
QDebug operator<<(QDebug debug, const DesktopEntry::Value &v);
-QDebug operator<<(QDebug debug, const DesktopErrorCode &v);
-
bool operator==(const DesktopEntry &lhs, const DesktopEntry &rhs);
bool operator!=(const DesktopEntry &lhs, const DesktopEntry &rhs);
diff --git a/src/desktopfileparser.cpp b/src/desktopfileparser.cpp
new file mode 100644
index 0000000..fa45a7e
--- /dev/null
+++ b/src/desktopfileparser.cpp
@@ -0,0 +1,159 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include
+#include "desktopfileparser.h"
+#include "constant.h"
+
+namespace {
+bool isInvalidLocaleString(const QString &str) noexcept
+{
+ constexpr auto Language = R"((?:[a-z]+))"; // language of locale postfix. eg.(en, zh)
+ constexpr auto Country = R"((?:_[A-Z]+))"; // country of locale postfix. eg.(US, CN)
+ constexpr auto Encoding = R"((?:\.[0-9A-Z-]+))"; // encoding of locale postfix. eg.(UFT-8)
+ constexpr auto Modifier = R"((?:@[a-zA-Z=;]+))"; // modifier of locale postfix. eg.(euro;collation=traditional)
+ const static auto validKey = QString(R"(^%1%2?%3?%4?$)").arg(Language, Country, Encoding, Modifier);
+ // example: https://regex101.com/r/hylOay/2
+ static const QRegularExpression _re = []() -> QRegularExpression {
+ QRegularExpression tmp{validKey};
+ tmp.optimize();
+ return tmp;
+ }();
+ thread_local const auto re = _re;
+
+ return re.match(str).hasMatch();
+}
+
+} // namespace
+
+ParserError DesktopFileParser::parse(Groups &ret) noexcept
+{
+ std::remove_reference_t groups;
+ while (!m_stream.atEnd()) {
+ auto err = addGroup(groups);
+ if (err != ParserError::NoError) {
+ return err;
+ }
+
+ if (groups.size() != 1) {
+ continue;
+ }
+
+ if (groups.keys().first() != DesktopFileEntryKey) {
+ qWarning() << "There should be nothing preceding "
+ "'Desktop Entry' group in the desktop entry file "
+ "but possibly one or more comments.";
+ return ParserError::InvalidFormat;
+ }
+ }
+
+ if (!m_line.isEmpty()) {
+ qCritical() << "Something is wrong in Desktop file parser, check logic.";
+ return ParserError::InternalError;
+ }
+
+ ret = std::move(groups);
+ return ParserError::NoError;
+}
+
+ParserError DesktopFileParser::addGroup(Groups &ret) noexcept
+{
+ skip();
+ if (!m_line.startsWith('[')) {
+ qWarning() << "Invalid desktop file format: unexpected line:" << m_line;
+ return ParserError::InvalidFormat;
+ }
+
+ // Parsing group header.
+ // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#group-header
+
+ auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
+
+ if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
+ qWarning() << "group header invalid:" << m_line;
+ return ParserError::InvalidFormat;
+ }
+
+ if (ret.find(groupHeader) != ret.end()) {
+ qWarning() << "duplicated group header detected:" << groupHeader;
+ return ParserError::InvalidFormat;
+ }
+
+ auto group = ret.insert(groupHeader, {});
+
+ m_line.clear();
+ while (!m_stream.atEnd() && !m_line.startsWith('[')) {
+ skip();
+ if (m_line.startsWith('[')) {
+ break;
+ }
+ auto err = addEntry(group);
+ if (err != ParserError::NoError) {
+ return err;
+ }
+ }
+ return ParserError::NoError;
+}
+
+ParserError DesktopFileParser::addEntry(typename Groups::iterator &group) noexcept
+{
+ auto line = m_line;
+ m_line.clear();
+ auto splitCharIndex = line.indexOf('=');
+ if (splitCharIndex == -1) {
+ qWarning() << "invalid line in desktop file, skip it:" << line;
+ return ParserError::NoError;
+ }
+ auto keyStr = line.first(splitCharIndex).trimmed();
+ auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
+
+ QString key{""};
+ QString localeStr{defaultKeyStr};
+ // NOTE:
+ // We are process "localized keys" here, for usage check:
+ // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
+ qsizetype localeBegin = keyStr.indexOf('[');
+ qsizetype localeEnd = keyStr.lastIndexOf(']');
+ if ((localeBegin == -1 && localeEnd != -1) || (localeBegin != -1 && localeEnd == -1)) {
+ qWarning() << "unmatched [] detected in desktop file, skip this line: " << line;
+ return ParserError::NoError;
+ }
+
+ if (localeBegin == -1 && localeEnd == -1) {
+ key = keyStr;
+ } else {
+ key = keyStr.sliced(0, localeBegin);
+ localeStr = keyStr.sliced(localeBegin + 1, localeEnd - localeBegin - 1); // strip '[' and ']'
+ }
+
+ static const QRegularExpression _re = []() {
+ QRegularExpression tmp{"R([^A-Za-z0-9-])"};
+ tmp.optimize();
+ return tmp;
+ }();
+ // NOTE: https://stackoverflow.com/a/25583104
+ thread_local const QRegularExpression re = _re;
+ if (re.match(key).hasMatch()) {
+ qWarning() << "invalid key name, skip this line:" << line;
+ return ParserError::NoError;
+ }
+
+ if (localeStr != defaultKeyStr && !isInvalidLocaleString(localeStr)) {
+ qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
+ }
+
+ auto keyIt = group->find(key);
+ if (keyIt != group->end() && keyIt->find(localeStr) != keyIt->end()) {
+ qWarning() << "duplicated localestring, skip this line:" << line;
+ return ParserError::NoError;
+ }
+
+ if (keyIt == group->end()) {
+ group->insert(key, {{localeStr, valueStr}});
+ return ParserError::NoError;
+ }
+
+ keyIt->insert(localeStr, valueStr);
+ return ParserError::NoError;
+}
diff --git a/src/desktopfileparser.h b/src/desktopfileparser.h
new file mode 100644
index 0000000..a0d0666
--- /dev/null
+++ b/src/desktopfileparser.h
@@ -0,0 +1,20 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef DESKTOPFILEPARSER_H
+#define DESKTOPFILEPARSER_H
+
+#include "iniParser.h"
+#include "desktopentry.h"
+
+class DesktopFileParser : public Parser
+{
+public:
+ using Parser::Parser;
+ ParserError parse(Groups &ret) noexcept override;
+ ParserError addGroup(Groups &ret) noexcept override;
+ ParserError addEntry(Groups::iterator &group) noexcept override;
+};
+
+#endif
diff --git a/src/global.h b/src/global.h
index 0e93d58..b867d09 100644
--- a/src/global.h
+++ b/src/global.h
@@ -29,10 +29,12 @@ Q_DECLARE_LOGGING_CATEGORY(DDEAMProf)
using ObjectInterfaceMap = QMap;
using ObjectMap = QMap;
-using PropMap = QMap>;
+using KVPairs = QMap;
+using PropMap = QMap;
Q_DECLARE_METATYPE(ObjectInterfaceMap)
Q_DECLARE_METATYPE(ObjectMap)
+Q_DECLARE_METATYPE(KVPairs)
Q_DECLARE_METATYPE(PropMap)
struct SystemdUnitDBusMessage
@@ -401,23 +403,36 @@ inline QStringList getDesktopFileDirs()
return XDGDataDirs;
}
-inline QStringList getAutoStartDirs()
+inline QString getXDGConfigHome()
+{
+ auto XDGConfigHome = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME"));
+ if (XDGConfigHome.isEmpty()) {
+ XDGConfigHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".config";
+ }
+
+ return XDGConfigHome;
+}
+
+inline QStringList getXDGConfigDirs()
{
auto XDGConfigDirs = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_DIRS")).split(':', Qt::SkipEmptyParts);
if (XDGConfigDirs.isEmpty()) {
XDGConfigDirs.append("/etc/xdg");
}
- auto XDGConfigHome = QString::fromLocal8Bit(qgetenv("XDG_CONFIG_HOME"));
- if (XDGConfigHome.isEmpty()) {
- XDGConfigHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".config";
- }
+ auto XDGConfigHome = getXDGConfigHome();
if (XDGConfigDirs.constFirst() != XDGConfigHome) {
XDGConfigDirs.removeAll(XDGConfigHome);
XDGConfigDirs.push_front(std::move(XDGConfigHome)); // guarantee XDG_CONFIG_HOME is first element.
}
+ return XDGConfigDirs;
+}
+
+inline QStringList getAutoStartDirs()
+{
+ auto XDGConfigDirs = getXDGConfigDirs();
std::for_each(XDGConfigDirs.begin(), XDGConfigDirs.end(), [](QString &str) {
if (!str.endsWith(QDir::separator())) {
str.append(QDir::separator());
@@ -428,6 +443,17 @@ inline QStringList getAutoStartDirs()
return XDGConfigDirs;
}
+inline QString getCurrentDesktop()
+{
+ auto desktops = QString::fromLocal8Bit(qgetenv("XDG_CURRENT_DESKTOP")).split(';', Qt::SkipEmptyParts);
+
+ if (desktops.size() > 1) {
+ qWarning() << "multi-DE is detected, use first value.";
+ }
+
+ return desktops.first();
+}
+
inline bool isApplication(const QDBusObjectPath &path)
{
return path.path().split('/').last().startsWith("app");
diff --git a/src/iniParser.h b/src/iniParser.h
new file mode 100644
index 0000000..846c93f
--- /dev/null
+++ b/src/iniParser.h
@@ -0,0 +1,108 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef INIPARSER_H
+#define INIPARSER_H
+
+#include
+#include
+#include
+#include
+
+enum class ParserError {
+ NoError,
+ NotFound,
+ MismatchedFile,
+ InvalidLocation,
+ InvalidFormat,
+ OpenFailed,
+ MissingInfo,
+ Parsed,
+ InternalError
+};
+
+template
+class Parser
+{
+public:
+ explicit Parser(QTextStream &stream)
+ : m_stream(stream){};
+ virtual ~Parser() = default;
+ using Groups = QMap>;
+
+ Parser(const Parser &) = delete;
+ Parser(Parser &&) = delete;
+ Parser &operator=(const Parser &) = delete;
+ Parser &operator=(Parser &&) = delete;
+
+ virtual ParserError parse(Groups &groups) noexcept = 0;
+ virtual ParserError addGroup(Groups &groups) noexcept = 0;
+ virtual ParserError addEntry(typename Groups::iterator &group) noexcept = 0;
+ void skip() noexcept
+ {
+ while (!m_stream.atEnd() and (m_line.startsWith('#') or m_line.isEmpty())) {
+ m_line = m_stream.readLine().trimmed();
+ }
+ };
+
+protected:
+ QTextStream &m_stream;
+ QString m_line;
+};
+
+inline bool hasNonAsciiAndControlCharacters(const QString &str) noexcept
+{
+ static const QRegularExpression _matchControlChars = []() {
+ QRegularExpression tmp{R"(\p{Cc})"};
+ tmp.optimize();
+ return tmp;
+ }();
+ thread_local const auto matchControlChars = _matchControlChars;
+ static const QRegularExpression _matchNonAsciiChars = []() {
+ QRegularExpression tmp{R"([^\x00-\x7f])"};
+ tmp.optimize();
+ return tmp;
+ }();
+ thread_local const auto matchNonAsciiChars = _matchNonAsciiChars;
+ return str.contains(matchControlChars) and str.contains(matchNonAsciiChars);
+}
+
+inline QDebug operator<<(QDebug debug, const ParserError &v)
+{
+ QDebugStateSaver saver{debug};
+ QString errMsg;
+ switch (v) {
+ case ParserError::NoError: {
+ errMsg = "no error.";
+ } break;
+ case ParserError::NotFound: {
+ errMsg = "file not found.";
+ } break;
+ case ParserError::MismatchedFile: {
+ errMsg = "file type is mismatched.";
+ } break;
+ case ParserError::InvalidLocation: {
+ errMsg = "file location is invalid, please check $XDG_DATA_DIRS.";
+ } break;
+ case ParserError::OpenFailed: {
+ errMsg = "couldn't open the file.";
+ } break;
+ case ParserError::InvalidFormat: {
+ errMsg = "the format of desktopEntry file is invalid.";
+ } break;
+ case ParserError::MissingInfo: {
+ errMsg = "missing required infomation.";
+ } break;
+ case ParserError::Parsed: {
+ errMsg = "this desktop entry is parsed.";
+ } break;
+ case ParserError::InternalError: {
+ errMsg = "internal error of parser.";
+ } break;
+ }
+ debug << errMsg;
+ return debug;
+}
+
+#endif
diff --git a/src/mimefileparser.cpp b/src/mimefileparser.cpp
new file mode 100644
index 0000000..9d71b06
--- /dev/null
+++ b/src/mimefileparser.cpp
@@ -0,0 +1,99 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include "mimefileparser.h"
+#include
+
+ParserError MimeFileParser::parse(Groups &ret) noexcept
+{
+ std::remove_reference_t groups;
+ while (!m_stream.atEnd()) {
+ auto err = addGroup(groups);
+ if (err != ParserError::NoError) {
+ ret.clear();
+ return err;
+ }
+ }
+
+ if (!m_line.isEmpty()) {
+ qCritical() << "Something is wrong in mimeapp.list parser, check logic.";
+ ret.clear();
+ return ParserError::InternalError;
+ }
+
+ ret = std::move(groups);
+ return ParserError::NoError;
+}
+
+ParserError MimeFileParser::addGroup(Groups &ret) noexcept
+{
+ skip();
+ if (!m_line.startsWith('[')) {
+ qWarning() << "Invalid mimeapp.list format: unexpected line:" << m_line;
+ return ParserError::InvalidFormat;
+ }
+
+ // Parsing group header, this format is same as desktop file's group
+
+ auto groupHeader = m_line.sliced(1, m_line.size() - 2).trimmed();
+
+ if (groupHeader.contains('[') || groupHeader.contains(']') || hasNonAsciiAndControlCharacters(groupHeader)) {
+ qWarning() << "group header invalid:" << m_line;
+ return ParserError::InvalidFormat;
+ }
+
+ if (m_desktopSpec and (groupHeader == addedAssociations or groupHeader == removedAssociations)) {
+ qWarning()
+ << "desktop-specific mimeapp.list is not possible to add or remove associations from these files, skip this group.";
+ while (!m_stream.atEnd() && !m_line.startsWith('[')) {
+ skip();
+ if (m_line.startsWith('[')) {
+ break;
+ }
+ }
+ return ParserError::NoError;
+ }
+
+ Groups::iterator group;
+ if (group = ret.find(groupHeader); group == ret.end()) {
+ group = ret.insert(groupHeader, {});
+ }
+
+ m_line.clear();
+ while (!m_stream.atEnd() && !m_line.startsWith('[')) {
+ skip();
+ if (m_line.startsWith('[')) {
+ break;
+ }
+ auto err = addEntry(group);
+ if (err != ParserError::NoError) {
+ return err;
+ }
+ }
+ return ParserError::NoError;
+}
+
+ParserError MimeFileParser::addEntry(Groups::iterator &group) noexcept
+{
+ auto line = m_line;
+ m_line.clear();
+ auto splitCharIndex = line.indexOf('=');
+ if (splitCharIndex == -1) {
+ qWarning() << "invalid line in desktop file, skip it:" << line;
+ return ParserError::NoError;
+ }
+ auto keyStr = line.first(splitCharIndex).trimmed();
+ auto valueStr = line.sliced(splitCharIndex + 1).trimmed();
+
+ if (valueStr.isEmpty()) {
+ return ParserError::InvalidFormat;
+ }
+
+ auto newValues = valueStr.split(';', Qt::SkipEmptyParts);
+ auto value = group->value(keyStr);
+ value.append(newValues);
+ group->insert(keyStr, newValues);
+
+ return ParserError::NoError;
+}
diff --git a/src/mimefileparser.h b/src/mimefileparser.h
new file mode 100644
index 0000000..93b8940
--- /dev/null
+++ b/src/mimefileparser.h
@@ -0,0 +1,33 @@
+// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef MIMEAPPFILEPARSER_H
+#define MIMEAPPFILEPARSER_H
+
+#include "iniParser.h"
+#include
+#include
+
+constexpr auto defaultApplications = "Default Applications";
+constexpr auto addedAssociations = "Added Associations";
+constexpr auto removedAssociations = "Removed Associations";
+constexpr auto mimeCache = "MIME Cache";
+
+class MimeFileParser : public Parser
+{
+public:
+ explicit MimeFileParser(QTextStream &stream, bool isDesktopSpecific)
+ : Parser(stream)
+ , m_desktopSpec(isDesktopSpecific)
+ {
+ }
+ ParserError parse(Groups &ret) noexcept override;
+ ParserError addGroup(Groups &ret) noexcept override;
+ ParserError addEntry(Groups::iterator &group) noexcept override;
+
+private:
+ bool m_desktopSpec;
+};
+
+#endif
diff --git a/tests/ut_desktopentry.cpp b/tests/ut_desktopentry.cpp
index f50c30c..f29a883 100644
--- a/tests/ut_desktopentry.cpp
+++ b/tests/ut_desktopentry.cpp
@@ -18,8 +18,8 @@ public:
env = qgetenv("XDG_DATA_DIRS");
auto curDir = QDir::current();
QByteArray fakeXDG = (curDir.absolutePath() + QDir::separator() + "data").toLocal8Bit();
- ASSERT_TRUE(qputenv("XDG_DATA_DIRS", fakeXDG)) ;
- DesktopErrorCode err;
+ ASSERT_TRUE(qputenv("XDG_DATA_DIRS", fakeXDG));
+ ParserError err;
auto file = DesktopFile::searchDesktopFileById("deepin-editor", err);
if (!file.has_value()) {
qWarning() << "search failed:" << err;
@@ -58,7 +58,7 @@ TEST_F(TestDesktopEntry, prase)
ASSERT_TRUE(in.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text));
QTextStream fs{&in};
auto err = entry.parse(fs);
- ASSERT_EQ(err, DesktopErrorCode::NoError);
+ ASSERT_EQ(err, ParserError::NoError);
auto group = entry.group("Desktop Entry");
ASSERT_TRUE(group);