2023-07-12 15:58:41 +08:00
|
|
|
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
|
|
|
|
//
|
|
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
|
|
|
|
#include "desktopentry.h"
|
2023-08-10 14:32:09 +08:00
|
|
|
#include "global.h"
|
2023-07-12 15:58:41 +08:00
|
|
|
#include <QFileInfo>
|
|
|
|
#include <QDir>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <QRegularExpression>
|
|
|
|
#include <QDirIterator>
|
|
|
|
#include <QStringView>
|
|
|
|
#include <QVariant>
|
|
|
|
#include <iostream>
|
2023-08-20 18:25:59 +08:00
|
|
|
#include <chrono>
|
2023-08-23 17:20:47 +08:00
|
|
|
#include <cstdio>
|
2023-07-12 15:58:41 +08:00
|
|
|
|
|
|
|
auto DesktopEntry::parserGroupHeader(const QString &str) noexcept
|
|
|
|
{
|
2023-08-25 10:47:17 +08:00
|
|
|
auto groupHeader = str.sliced(1, str.size() - 2).trimmed();
|
|
|
|
decltype(m_entryMap)::iterator it{m_entryMap.end()};
|
|
|
|
|
|
|
|
QRegularExpression re{R"([^\x20-\x5a-\x5e-\x7e\x5c])"};
|
|
|
|
auto matcher = re.match(groupHeader);
|
|
|
|
if (matcher.hasMatch()) {
|
|
|
|
return it;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto tmp = m_entryMap.find(groupHeader);
|
|
|
|
if (tmp == m_entryMap.end()) {
|
|
|
|
it = m_entryMap.insert(groupHeader, {});
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
|
2023-08-10 14:32:09 +08:00
|
|
|
return it;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
QString DesktopFile::sourcePath() const noexcept
|
|
|
|
{
|
2023-08-25 10:47:17 +08:00
|
|
|
if (!m_fileSource) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
QFileInfo info(*m_fileSource);
|
|
|
|
return info.absoluteFilePath();
|
|
|
|
}
|
2023-08-20 18:25:59 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
bool DesktopEntry::isInvalidLocaleString(const QString &str) noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
2023-08-25 10:47:17 +08:00
|
|
|
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-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 QRegularExpression re = []() -> QRegularExpression {
|
|
|
|
QRegularExpression tmp{validKey};
|
|
|
|
tmp.optimize();
|
|
|
|
return tmp;
|
|
|
|
}();
|
2023-07-24 14:12:59 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
return re.match(str).hasMatch();
|
|
|
|
}
|
|
|
|
|
|
|
|
QPair<QString, QString> DesktopEntry::processEntry(const QString &str) noexcept
|
|
|
|
{
|
2023-07-12 15:58:41 +08:00
|
|
|
auto splitCharIndex = str.indexOf(']');
|
2023-07-21 14:47:40 +08:00
|
|
|
if (splitCharIndex != -1) {
|
2023-07-12 15:58:41 +08:00
|
|
|
for (; splitCharIndex < str.size(); ++splitCharIndex) {
|
2023-08-10 14:32:09 +08:00
|
|
|
if (str.at(splitCharIndex) == '=') {
|
2023-07-12 15:58:41 +08:00
|
|
|
break;
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
splitCharIndex = str.indexOf('=');
|
|
|
|
}
|
|
|
|
auto keyStr = str.first(splitCharIndex).trimmed();
|
|
|
|
auto valueStr = str.sliced(splitCharIndex + 1).trimmed();
|
2023-08-25 10:47:17 +08:00
|
|
|
return qMakePair(std::move(keyStr), std::move(valueStr));
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<QPair<QString, QString>> DesktopEntry::processEntryKey(const QString &keyStr) noexcept
|
|
|
|
{
|
2023-08-10 14:32:09 +08:00
|
|
|
QString key;
|
2023-08-25 10:47:17 +08:00
|
|
|
QString localeStr;
|
2023-08-25 11:16:01 +08:00
|
|
|
// NOTE:
|
|
|
|
// We are process "localized keys" here, for usage check:
|
|
|
|
// https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#localized-keys
|
2023-08-25 10:47:17 +08:00
|
|
|
if (auto index = keyStr.indexOf('['); index != -1) {
|
|
|
|
key = keyStr.sliced(0, index);
|
|
|
|
localeStr = keyStr.sliced(index + 1, keyStr.length() - 1 - index - 1); // strip '[' and ']'
|
2023-08-25 11:16:01 +08:00
|
|
|
if (!isInvalidLocaleString(localeStr)) {
|
|
|
|
qWarning().noquote() << QString("invalid LOCALE (%2) for key \"%1\"").arg(key, localeStr);
|
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
} else {
|
|
|
|
key = keyStr;
|
|
|
|
}
|
2023-08-21 18:38:27 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
QRegularExpression re{"R([^A-Za-z0-9-])"};
|
|
|
|
if (re.match(key).hasMatch()) {
|
|
|
|
qWarning() << "keyName's format is invalid.";
|
|
|
|
return std::nullopt;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
return qMakePair(std::move(key), std::move(localeStr));
|
|
|
|
}
|
|
|
|
|
|
|
|
DesktopErrorCode DesktopEntry::parseEntry(const QString &str, decltype(m_entryMap)::iterator ¤tGroup) noexcept
|
|
|
|
{
|
|
|
|
auto [key, value] = processEntry(str);
|
|
|
|
auto keyPair = processEntryKey(key);
|
|
|
|
|
|
|
|
if (!keyPair.has_value()) {
|
|
|
|
return DesktopErrorCode::InvalidFormat;
|
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
auto [keyName, localeStr] = std::move(keyPair).value();
|
|
|
|
if (localeStr.isEmpty()) {
|
|
|
|
localeStr = defaultKeyStr;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-10 14:32:09 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
auto valueIt = currentGroup->find(keyName);
|
|
|
|
if (valueIt == currentGroup->end()) {
|
|
|
|
currentGroup->insert(keyName, {{localeStr, value}});
|
2023-08-11 17:46:46 +08:00
|
|
|
return DesktopErrorCode::NoError;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-10 14:32:09 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
auto innerIt = valueIt->find(localeStr);
|
|
|
|
if (innerIt == valueIt->end()) {
|
|
|
|
valueIt->insert(localeStr, value);
|
2023-08-11 17:46:46 +08:00
|
|
|
return DesktopErrorCode::NoError;
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
qWarning() << "duplicated postfix and this line will be aborted, maybe format is invalid.\n"
|
2023-08-25 10:47:17 +08:00
|
|
|
<< "exist: " << innerIt.key() << "[" << innerIt.value() << "]"
|
|
|
|
<< "current: " << keyName << "[" << localeStr << "]";
|
2023-08-10 14:32:09 +08:00
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
return DesktopErrorCode::NoError;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
bool DesktopEntry::checkMainEntryValidation() const noexcept
|
|
|
|
{
|
|
|
|
auto it = m_entryMap.find(DesktopFileEntryKey);
|
|
|
|
if (it == m_entryMap.end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (auto name = it->find("Name"); name == it->end()) {
|
|
|
|
qWarning() << "No Name.";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto type = it->find("Type");
|
|
|
|
if (type == it->end()) {
|
|
|
|
qWarning() << "No Type.";
|
|
|
|
for (const auto &[k, v] : it->asKeyValueRange()) {
|
|
|
|
qInfo() << "keyName:" << k;
|
|
|
|
for (const auto &[key, value] : v.asKeyValueRange()) {
|
|
|
|
qInfo() << key << value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto &typeStr = *type->find(defaultKeyStr);
|
|
|
|
|
|
|
|
if (typeStr == "Link") {
|
|
|
|
if (it->find("URL") == it->end()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
std::optional<DesktopFile> DesktopFile::createTemporaryDesktopFile(std::unique_ptr<QFile> temporaryFile) noexcept
|
2023-08-20 18:25:59 +08:00
|
|
|
{
|
2023-08-21 16:02:26 +08:00
|
|
|
auto mtime = getFileModifiedTime(*temporaryFile);
|
|
|
|
if (mtime == 0) {
|
|
|
|
qWarning() << "create temporary file failed.";
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
return DesktopFile{std::move(temporaryFile), "", mtime};
|
2023-08-20 18:25:59 +08:00
|
|
|
}
|
|
|
|
|
2023-08-23 17:20:47 +08:00
|
|
|
std::optional<DesktopFile> DesktopFile::createTemporaryDesktopFile(const QString &temporaryFile) noexcept
|
|
|
|
{
|
|
|
|
const static QString userTmp = QString{"/run/user/%1/"}.arg(getCurrentUID());
|
|
|
|
auto tempFile = std::make_unique<QFile>(QString{userTmp + QUuid::createUuid().toString(QUuid::Id128) + ".desktop"});
|
|
|
|
|
|
|
|
if (!tempFile->open(QFile::NewOnly | QFile::WriteOnly | QFile::Text)) {
|
|
|
|
qWarning() << "failed to create temporary desktop file:" << QFileInfo{*tempFile}.absoluteFilePath()
|
|
|
|
<< tempFile->errorString();
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto content = temporaryFile.toLocal8Bit();
|
|
|
|
auto writeByte = tempFile->write(content);
|
|
|
|
|
|
|
|
if (writeByte == -1 || writeByte != content.length()) {
|
|
|
|
qWarning() << "write to temporary file failed:" << tempFile->errorString();
|
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
tempFile->close();
|
|
|
|
|
|
|
|
return createTemporaryDesktopFile(std::move(tempFile));
|
|
|
|
}
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
std::optional<DesktopFile> DesktopFile::searchDesktopFileByPath(const QString &desktopFile, DesktopErrorCode &err) noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
2023-08-23 17:20:47 +08:00
|
|
|
decltype(auto) desktopSuffix = ".desktop";
|
2023-08-10 14:32:09 +08:00
|
|
|
|
2023-08-14 16:30:16 +08:00
|
|
|
if (!desktopFile.endsWith(desktopSuffix)) {
|
2023-08-11 17:46:46 +08:00
|
|
|
qWarning() << "file isn't a desktop file:" << desktopFile;
|
|
|
|
err = DesktopErrorCode::MismatchedFile;
|
|
|
|
return std::nullopt;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
QFileInfo fileinfo{desktopFile};
|
|
|
|
if (!fileinfo.isAbsolute() or !fileinfo.exists()) {
|
2023-07-12 15:58:41 +08:00
|
|
|
qWarning() << "desktop file not found.";
|
2023-08-11 17:46:46 +08:00
|
|
|
err = DesktopErrorCode::NotFound;
|
2023-07-12 15:58:41 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
|
|
|
|
QString path{desktopFile};
|
|
|
|
QString id;
|
|
|
|
|
2023-08-23 17:20:47 +08:00
|
|
|
const auto &XDGDataDirs = getDesktopFileDirs();
|
2023-08-11 17:46:46 +08:00
|
|
|
auto idGen = std::any_of(XDGDataDirs.cbegin(), XDGDataDirs.cend(), [&desktopFile](const QString &suffixPath) {
|
|
|
|
return desktopFile.startsWith(suffixPath);
|
|
|
|
});
|
|
|
|
|
|
|
|
if (idGen) {
|
2023-08-14 16:30:16 +08:00
|
|
|
auto tmp = path.chopped(sizeof(desktopSuffix) - 1);
|
2023-08-11 17:46:46 +08:00
|
|
|
auto components = tmp.split(QDir::separator()).toList();
|
|
|
|
auto it = std::find(components.cbegin(), components.cend(), "applications");
|
2023-07-19 17:56:45 +08:00
|
|
|
QString FileId;
|
|
|
|
++it;
|
2023-08-11 17:46:46 +08:00
|
|
|
while (it != components.cend()) {
|
2023-07-19 17:56:45 +08:00
|
|
|
FileId += (*(it++) + "-");
|
2023-08-11 17:46:46 +08:00
|
|
|
}
|
2023-07-24 14:12:59 +08:00
|
|
|
id = FileId.chopped(1); // remove extra "-""
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-10 14:32:09 +08:00
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
auto filePtr = std::make_unique<QFile>(std::move(path));
|
|
|
|
|
|
|
|
auto mtime = getFileModifiedTime(*filePtr);
|
|
|
|
|
|
|
|
if (mtime == 0) {
|
2023-08-11 17:46:46 +08:00
|
|
|
err = DesktopErrorCode::OpenFailed;
|
2023-08-10 14:32:09 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
err = DesktopErrorCode::NoError;
|
2023-08-21 16:02:26 +08:00
|
|
|
return DesktopFile{std::move(filePtr), std::move(id), mtime};
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
std::optional<DesktopFile> DesktopFile::searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept
|
|
|
|
{
|
2023-08-23 17:20:47 +08:00
|
|
|
auto XDGDataDirs = getDesktopFileDirs();
|
2023-08-14 16:30:16 +08:00
|
|
|
constexpr auto desktopSuffix = u8".desktop";
|
2023-08-11 17:46:46 +08:00
|
|
|
|
|
|
|
for (const auto &dir : XDGDataDirs) {
|
2023-08-14 16:30:16 +08:00
|
|
|
auto app = QFileInfo{dir + QDir::separator() + appId + desktopSuffix};
|
2023-08-11 17:46:46 +08:00
|
|
|
while (!app.exists()) {
|
|
|
|
auto filePath = app.absoluteFilePath();
|
|
|
|
auto hyphenIndex = filePath.indexOf('-');
|
|
|
|
if (hyphenIndex == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
filePath.replace(hyphenIndex, 1, QDir::separator());
|
|
|
|
app.setFile(filePath);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (app.exists()) {
|
|
|
|
return searchDesktopFileByPath(app.absoluteFilePath(), err);
|
|
|
|
}
|
|
|
|
}
|
2023-08-14 16:30:16 +08:00
|
|
|
|
|
|
|
err = DesktopErrorCode::NotFound;
|
2023-08-11 17:46:46 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
2023-08-10 14:32:09 +08:00
|
|
|
bool DesktopFile::modified(std::size_t time) const noexcept
|
|
|
|
{
|
|
|
|
return time != m_mtime;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
DesktopErrorCode DesktopEntry::parse(DesktopFile &file) noexcept
|
2023-07-24 14:12:59 +08:00
|
|
|
{
|
2023-08-21 16:02:26 +08:00
|
|
|
DesktopFileGuard guard{file};
|
|
|
|
|
|
|
|
if (!guard.try_open()) {
|
|
|
|
qWarning() << file.sourcePath() << "can't open.";
|
|
|
|
return DesktopErrorCode::OpenFailed;
|
2023-07-24 14:12:59 +08:00
|
|
|
}
|
|
|
|
|
2023-08-21 16:02:26 +08:00
|
|
|
QTextStream stream;
|
|
|
|
stream.setDevice(file.sourceFile());
|
|
|
|
return parse(stream);
|
2023-07-24 14:12:59 +08:00
|
|
|
}
|
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
bool DesktopEntry::skipCheck(const QString &line) noexcept
|
|
|
|
{
|
|
|
|
return line.startsWith('#') or line.isEmpty();
|
|
|
|
}
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
DesktopErrorCode DesktopEntry::parse(QTextStream &stream) noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
2023-08-10 14:32:09 +08:00
|
|
|
if (stream.atEnd()) {
|
2023-08-25 10:47:17 +08:00
|
|
|
if (m_context == EntryContext::Done) {
|
|
|
|
return DesktopErrorCode::NoError;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
return DesktopErrorCode::OpenFailed;
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
|
|
|
|
stream.setEncoding(QStringConverter::Utf8);
|
|
|
|
decltype(m_entryMap)::iterator currentGroup;
|
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
DesktopErrorCode err{DesktopErrorCode::NoError};
|
2023-08-25 10:47:17 +08:00
|
|
|
bool mainEntryParsed{false};
|
|
|
|
QString line;
|
2023-07-12 15:58:41 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
while (!stream.atEnd()) {
|
|
|
|
switch (m_context) {
|
|
|
|
case EntryContext::Unknown: {
|
|
|
|
qWarning() << "entry context is unknown,abort.";
|
|
|
|
err = DesktopErrorCode::InvalidFormat;
|
|
|
|
return err;
|
|
|
|
} break;
|
|
|
|
case EntryContext::EntryOuter: {
|
|
|
|
if (skipCheck(line)) {
|
|
|
|
line = stream.readLine().trimmed();
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (line.startsWith('[')) {
|
|
|
|
auto group = parserGroupHeader(line);
|
|
|
|
|
|
|
|
if (group == m_entryMap.end()) {
|
|
|
|
qWarning() << "groupName format error or already exists:" << line;
|
|
|
|
return DesktopErrorCode::InvalidFormat;
|
|
|
|
}
|
|
|
|
currentGroup = group;
|
|
|
|
bool isMainEntry = (currentGroup.key() == DesktopFileEntryKey);
|
|
|
|
|
|
|
|
if ((!mainEntryParsed and isMainEntry) or (mainEntryParsed and !isMainEntry)) {
|
|
|
|
m_context = EntryContext::Entry;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
qWarning() << "groupName format error:" << line;
|
|
|
|
err = DesktopErrorCode::InvalidFormat;
|
|
|
|
return err;
|
|
|
|
} break;
|
|
|
|
case EntryContext::Entry: {
|
|
|
|
line = stream.readLine().trimmed();
|
|
|
|
|
|
|
|
if (skipCheck(line)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (line.startsWith('[')) {
|
|
|
|
m_context = EntryContext::EntryOuter;
|
|
|
|
|
|
|
|
if (currentGroup.key() == DesktopFileEntryKey) {
|
|
|
|
mainEntryParsed = true;
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = parseEntry(line, currentGroup);
|
|
|
|
if (err != DesktopErrorCode::NoError) {
|
|
|
|
qWarning() << "Entry format error:" << line;
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case EntryContext::Done:
|
|
|
|
break;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
|
2023-08-25 10:47:17 +08:00
|
|
|
m_context = EntryContext::Done;
|
|
|
|
if (!checkMainEntryValidation()) {
|
|
|
|
qWarning() << "invalid MainEntry, abort.";
|
|
|
|
err = DesktopErrorCode::MissingInfo;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
|
2023-07-12 15:58:41 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2023-07-24 14:12:59 +08:00
|
|
|
std::optional<QMap<QString, DesktopEntry::Value>> DesktopEntry::group(const QString &key) const noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
2023-08-10 14:32:09 +08:00
|
|
|
if (auto group = m_entryMap.find(key); group != m_entryMap.cend()) {
|
2023-07-12 15:58:41 +08:00
|
|
|
return *group;
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
2023-07-24 14:12:59 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<DesktopEntry::Value> DesktopEntry::value(const QString &groupKey, const QString &valueKey) const noexcept
|
|
|
|
{
|
|
|
|
const auto &destGroup = group(groupKey);
|
|
|
|
if (!destGroup) {
|
2023-08-16 17:44:56 +08:00
|
|
|
#ifdef DEBUG_MODE
|
2023-07-24 14:12:59 +08:00
|
|
|
qWarning() << "group " << groupKey << " can't be found.";
|
2023-08-16 17:44:56 +08:00
|
|
|
#endif
|
2023-07-24 14:12:59 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto it = destGroup->find(valueKey);
|
|
|
|
if (it == destGroup->cend()) {
|
2023-08-16 17:44:56 +08:00
|
|
|
#ifdef DEBUG_MODE
|
2023-07-24 14:12:59 +08:00
|
|
|
qWarning() << "value " << valueKey << " can't be found.";
|
2023-08-16 17:44:56 +08:00
|
|
|
#endif
|
2023-07-24 14:12:59 +08:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
return *it;
|
2023-07-12 15:58:41 +08:00
|
|
|
}
|
|
|
|
|
2023-08-10 14:32:09 +08:00
|
|
|
QString DesktopEntry::Value::unescape(const QString &str) noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
|
|
|
QString unescapedStr;
|
|
|
|
for (qsizetype i = 0; i < str.size(); ++i) {
|
|
|
|
auto c = str.at(i);
|
|
|
|
if (c != '\\') {
|
|
|
|
unescapedStr.append(c);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (str.at(i + 1).toLatin1()) {
|
|
|
|
default:
|
|
|
|
unescapedStr.append(c);
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
|
|
unescapedStr.append('\n');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
case 't':
|
|
|
|
unescapedStr.append('\t');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
case 'r':
|
|
|
|
unescapedStr.append('\r');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
case '\\':
|
|
|
|
unescapedStr.append('\\');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
case ';':
|
|
|
|
unescapedStr.append(';');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
case 's':
|
|
|
|
unescapedStr.append(' ');
|
|
|
|
++i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return unescapedStr;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString DesktopEntry::Value::toString(bool &ok) const noexcept
|
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
auto str = this->find(defaultKeyStr);
|
2023-08-10 14:32:09 +08:00
|
|
|
if (str == this->end()) {
|
2023-07-12 15:58:41 +08:00
|
|
|
return {};
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
auto unescapedStr = unescape(*str);
|
|
|
|
constexpr auto controlChars = "\\p{Cc}";
|
|
|
|
constexpr auto asciiChars = "[^\x00-\x7f]";
|
2023-08-10 14:32:09 +08:00
|
|
|
if (unescapedStr.contains(QRegularExpression{controlChars}) and unescapedStr.contains(QRegularExpression{asciiChars})) {
|
2023-07-12 15:58:41 +08:00
|
|
|
return {};
|
2023-08-10 14:32:09 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
|
|
|
|
ok = true;
|
|
|
|
return unescapedStr;
|
|
|
|
}
|
|
|
|
|
2023-07-21 14:47:40 +08:00
|
|
|
QString DesktopEntry::Value::toLocaleString(const QLocale &locale, bool &ok) const noexcept
|
2023-07-12 15:58:41 +08:00
|
|
|
{
|
|
|
|
ok = false;
|
|
|
|
for (auto it = this->constKeyValueBegin(); it != this->constKeyValueEnd(); ++it) {
|
|
|
|
auto [a, b] = *it;
|
|
|
|
if (QLocale{a}.name() == locale.name()) {
|
|
|
|
ok = true;
|
|
|
|
return unescape(b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return toString(ok);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString DesktopEntry::Value::toIconString(bool &ok) const noexcept
|
|
|
|
{
|
|
|
|
return toString(ok);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DesktopEntry::Value::toBoolean(bool &ok) const noexcept
|
|
|
|
{
|
2023-07-19 17:56:45 +08:00
|
|
|
ok = false;
|
2023-07-21 14:47:40 +08:00
|
|
|
const auto &str = (*this)[defaultKeyStr];
|
2023-07-19 17:56:45 +08:00
|
|
|
if (str == "true") {
|
|
|
|
ok = true;
|
2023-07-12 15:58:41 +08:00
|
|
|
return true;
|
2023-07-19 17:56:45 +08:00
|
|
|
}
|
|
|
|
if (str == "false") {
|
|
|
|
ok = true;
|
2023-07-12 15:58:41 +08:00
|
|
|
return false;
|
2023-07-19 17:56:45 +08:00
|
|
|
}
|
2023-07-12 15:58:41 +08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
float DesktopEntry::Value::toNumeric(bool &ok) const noexcept
|
|
|
|
{
|
2023-07-21 14:47:40 +08:00
|
|
|
const auto &str = (*this)[defaultKeyStr];
|
2023-07-12 15:58:41 +08:00
|
|
|
QVariant v{str};
|
|
|
|
return v.toFloat(&ok);
|
|
|
|
}
|
|
|
|
|
|
|
|
QDebug operator<<(QDebug debug, const DesktopEntry::Value &v)
|
|
|
|
{
|
|
|
|
QDebugStateSaver saver{debug};
|
2023-07-21 14:47:40 +08:00
|
|
|
debug << static_cast<const QMap<QString, QString> &>(v);
|
2023-07-12 15:58:41 +08:00
|
|
|
return debug;
|
|
|
|
}
|
2023-07-19 17:56:45 +08:00
|
|
|
|
2023-08-11 17:46:46 +08:00
|
|
|
QDebug operator<<(QDebug debug, const DesktopErrorCode &v)
|
2023-07-19 17:56:45 +08:00
|
|
|
{
|
|
|
|
QDebugStateSaver saver{debug};
|
|
|
|
QString errMsg;
|
|
|
|
switch (v) {
|
2023-08-11 17:46:46 +08:00
|
|
|
case DesktopErrorCode::NoError: {
|
2023-07-19 17:56:45 +08:00
|
|
|
errMsg = "no error.";
|
|
|
|
break;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
case DesktopErrorCode::NotFound: {
|
2023-07-19 17:56:45 +08:00
|
|
|
errMsg = "file not found.";
|
|
|
|
break;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
case DesktopErrorCode::MismatchedFile: {
|
2023-07-19 17:56:45 +08:00
|
|
|
errMsg = "file type is mismatched.";
|
|
|
|
break;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
case DesktopErrorCode::InvalidLocation: {
|
2023-07-19 17:56:45 +08:00
|
|
|
errMsg = "file location is invalid, please check $XDG_DATA_DIRS.";
|
|
|
|
break;
|
|
|
|
}
|
2023-08-11 17:46:46 +08:00
|
|
|
case DesktopErrorCode::OpenFailed: {
|
2023-07-19 17:56:45 +08:00
|
|
|
errMsg = "couldn't open the file.";
|
|
|
|
break;
|
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
case DesktopErrorCode::InvalidFormat: {
|
|
|
|
errMsg = "the format of desktopEntry file is invalid.";
|
2023-07-19 17:56:45 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-08-25 10:47:17 +08:00
|
|
|
case DesktopErrorCode::MissingInfo: {
|
|
|
|
errMsg = "missing required infomation.";
|
2023-07-19 17:56:45 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
debug << errMsg;
|
|
|
|
return debug;
|
|
|
|
}
|