dde-application-manager/src/lib/desktopinfo.cpp
Chen Linxuan accc9c2aac fix: introduce new DesktopFile class
KeyFile::saveToFile not guaranteed section order, which is not the right
behavior of desktop file. XDG requires the first section of desktop file
always to be "Desktop Entry".

So I write a new DesktopFile class, which override the saveToFile method
of KeyFile, to make it work.

close linuxdeepin/developer-center#3807

Signed-off-by: black-desk <me@black-desk.cn>
2023-04-03 01:42:34 +00:00

412 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2018 - 2023 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "desktopinfo.h"
#include "locale.h"
#include "unistd.h"
#include "dstring.h"
#include "dfile.h"
#include "basedir.h"
#include <QDebug>
#include <algorithm>
#include <stdlib.h>
#include <iostream>
#include <dirent.h>
std::vector<std::string> DesktopInfo::currentDesktops;
DesktopInfo::DesktopInfo(const std::string &_fileName)
: m_isValid(true)
, m_desktopFile()
{
std::string fileNameWithSuffix(_fileName);
if (!DString::endWith(_fileName, ".desktop"))
fileNameWithSuffix += ".desktop";
m_fileName = fileNameWithSuffix;
if (DFile::dir(m_fileName).empty()) {
// fileName是文件名增加目录
bool isExisted = false;
for (const auto &dir : BaseDir::appDirs()) {
m_fileName = dir + fileNameWithSuffix;
if (DFile::isExisted(m_fileName)) {
isExisted = true;
break;
}
}
if (!isExisted) {
m_isValid = false;
return;
}
}
m_desktopFile.loadFile(m_fileName);
// check DesktopInfo valid
std::vector<std::string> mainKeys = m_desktopFile.getMainKeys();
if (mainKeys.size() == 0)
m_isValid = false;
bool found = std::any_of(mainKeys.begin(), mainKeys.end(),
[](const auto &key) {return key == MainSection;});
if (!found)
m_isValid = false;
if (m_desktopFile.getStr(MainSection, KeyType) != TypeApplication)
m_isValid = false;
m_name = m_desktopFile.getLocaleStr(MainSection, KeyName, "");
m_icon = m_desktopFile.getStr(MainSection, KeyIcon);
m_id = getId();
}
DesktopInfo::~DesktopInfo()
{
}
std::string DesktopInfo::getFileName()
{
return m_fileName;
}
bool DesktopInfo::isValidDesktop()
{
return m_isValid;
}
/** if return true, item is shown
* @brief DesktopInfo::shouldShow
* @return
*/
bool DesktopInfo::shouldShow()
{
if (getNoDisplay() || getIsHidden()) {
qDebug() << "hidden desktop file path: " << QString::fromStdString(m_fileName);
return false;
}
std::vector<std::string> desktopEnvs;
return getShowIn(desktopEnvs);
}
bool DesktopInfo::getNoDisplay()
{
return m_desktopFile.getBool(MainSection, KeyNoDisplay);
}
bool DesktopInfo::getIsHidden()
{
return m_desktopFile.getBool(MainSection, KeyHidden);
}
bool DesktopInfo::getShowIn(std::vector<std::string> desktopEnvs)
{
#ifdef QT_DEBUG
qDebug() << "desktop file path: " << QString::fromStdString(m_fileName);
#endif
if (desktopEnvs.size() == 0) {
const char *env = getenv(envDesktopEnv.c_str());
const auto &desktop = DString::splitChars(env, ':');
currentDesktops.assign(desktop.begin(), desktop.end());
desktopEnvs.assign(currentDesktops.begin(), currentDesktops.end());
}
std::vector<std::string> onlyShowIn = m_desktopFile.getStrList(MainSection, KeyOnlyShowIn);
std::vector<std::string> notShowIn = m_desktopFile.getStrList(MainSection, KeyNotShowIn);
#ifdef QT_DEBUG
auto strVector2qstrVector = [](const std::vector<std::string> &vector) {
QVector<QString> vectorString;
for (const std::string &str : vector)
vectorString.append(QString::fromStdString(str));
return vectorString;
};
qDebug() << "onlyShowIn:" << strVector2qstrVector(onlyShowIn) <<
", notShowIn:" << strVector2qstrVector(notShowIn) <<
", desktopEnvs:" << strVector2qstrVector(desktopEnvs);
#endif
for (const auto &desktop : desktopEnvs) {
bool ret = std::any_of(onlyShowIn.begin(), onlyShowIn.end(),
[&desktop](const auto &d) {return d == desktop;});
#ifdef QT_DEBUG
qInfo() << Q_FUNC_INFO << "onlyShowIn, result:" << ret;
#endif
if (ret)
return true;
ret = std::any_of(notShowIn.begin(), notShowIn.end(),
[&desktop](const auto &d) {return d == desktop;});
#ifdef QT_DEBUG
qInfo() << Q_FUNC_INFO << "notShowIn, result:" << ret;
#endif
if (ret)
return false;
}
return onlyShowIn.size() == 0;
}
std::string DesktopInfo::getExecutable()
{
return m_desktopFile.getStr(MainSection, KeyExec);
}
bool DesktopInfo::isExecutableOk()
{
// 检查TryExec字段
std::string value = getTryExec();
std::vector<std::string> parts = DString::splitStr(value, ' ');
if (parts.size() > 0 ) {
value.assign(parts[0]);
DString::delQuote(value);
if (strstr(value.c_str(), "/") && DFile::isExisted(value))
return true;
else
return findExecutable(value);
}
// 检查Exec字段
value.assign(getExecutable());
parts.clear();
parts = DString::splitStr(value, ' ');
if (parts.size() > 0) {
value.assign(parts[0]);
DString::delQuote(value);
if (strstr(value.c_str(), "/") && DFile::isExisted(value))
return true;
else
return findExecutable(value);
}
return false;
}
bool DesktopInfo::isInstalled()
{
const char *name = m_fileName.c_str();
const char *found = strstr(name, "/applications/");
if (!found)
return false;
auto appDirs = BaseDir::appDirs();
return std::any_of(appDirs.begin(), appDirs.end(),
[&name, &found] (std::string dir) -> bool {return strneq(dir.c_str(), name, size_t(found - name));});
}
// [Desktop Action new-window] or [Full_Screenshot Shortcut Group]
bool DesktopInfo::isDesktopAction(std::string name)
{
return DString::startWith(name.c_str(), "Desktop Action") || DString::endWith(name.c_str(), "Shortcut Group");
}
std::vector<DesktopAction> DesktopInfo::getActions()
{
std::vector<DesktopAction> actions;
for (const auto &mainKey : m_desktopFile.getMainKeys()) {
if (DString::startWith(mainKey, "Desktop Action")
|| DString::endWith(mainKey, "Shortcut Group")) {
DesktopAction action;
action.name = m_desktopFile.getLocaleStr(mainKey, KeyName, "");
action.exec = m_desktopFile.getStr(mainKey, KeyExec);
action.section = mainKey;
actions.push_back(action);
}
}
return actions;
}
// 使用appId获取DesktopInfo需检查有效性
DesktopInfo DesktopInfo::getDesktopInfoById(std::string appId)
{
if (!DString::endWith(appId, ".desktop"))
appId += ".desktop";
for (const auto & dir : BaseDir::appDirs()) {
std::string filePath = dir + appId;
//检测文件有效性
if (DFile::isExisted(filePath)) {
return DesktopInfo(filePath);
}
}
return DesktopInfo("");
}
bool DesktopInfo::getTerminal()
{
return m_desktopFile.getBool(MainSection, KeyTerminal);
}
// TryExec is Path to an executable file on disk used to determine if the program is actually installed
std::string DesktopInfo::getTryExec()
{
return m_desktopFile.getStr(MainSection, KeyTryExec);
}
// 按$PATH路径查找执行文件
bool DesktopInfo::findExecutable(std::string &exec)
{
static const char *path = getenv("PATH");
static std::vector<std::string> paths = DString::splitChars(path, ':');
return std::any_of(paths.begin(), paths.end(), [&exec](std::string path) {return DFile::isExisted(path + "/" +exec);});
}
/**
* @brief DesktopInfo::getId
* filename must has suffix desktopExt
* example:
* /usr/share/applications/a.desktop -> a
* /usr/share/applications/kde4/a.desktop -> kde4/a
* /xxxx/dir/a.desktop -> /xxxx/dir/a
* @return
*/
std::string DesktopInfo::getId()
{
if (!m_id.empty())
return m_id;
std::string idStr;
auto const suffixPos = m_fileName.find(".desktop");
if (suffixPos == std::string::npos)
return "";
idStr = m_fileName.substr(0, m_fileName.size() - 8); // trim suffix
size_t dirPos = idStr.find("/applications/");
if (dirPos == std::string::npos)
return idStr;
std::string baseDir(idStr.substr(0, dirPos + 14)); // length of "/applications/" is 14
std::vector<std::string> appDirs = BaseDir::appDirs();
bool installed = std::any_of(appDirs.begin(), appDirs.end(),
[&baseDir](const auto &dir) {return dir == baseDir;});
if (installed) {
m_id = idStr.substr(baseDir.size(), idStr.size());
}
return m_id;
}
std::string DesktopInfo::getGenericName()
{
return m_desktopFile.getLocaleStr(MainSection, KeyGenericName, "");
}
std::string DesktopInfo::getName()
{
return m_name;
}
std::string DesktopInfo::getIcon()
{
return m_icon;
}
std::string DesktopInfo::getCommandLine()
{
return m_desktopFile.getStr(MainSection, KeyExec);
}
std::vector<std::string> DesktopInfo::getKeywords()
{
return m_desktopFile.getLocaleStrList(MainSection, KeyKeywords, "");
}
std::vector<std::string> DesktopInfo::getCategories()
{
return m_desktopFile.getStrList(MainSection, KeyCategories);
}
void DesktopInfo::setDesktopOverrideExec(const std::string &execStr)
{
m_overRideExec = execStr;
}
DesktopFile *DesktopInfo::getDesktopFile()
{
return &m_desktopFile;
}
// class AppsDir
AppsDir::AppsDir(const std::string &dirPath)
: m_path(dirPath)
{
}
AppsDir::~AppsDir()
{
}
std::string AppsDir::getPath()
{
return m_path;
}
// 获取目录对应的应用名称
std::map<std::string, bool> AppsDir::getAppNames()
{
DIR* dp;
struct dirent* ep;
dp = opendir(m_path.c_str());
if (!dp) {
std::cout << "Couldn't open directory " << m_path << std::endl;
return m_appNames;
}
while ((ep = readdir(dp))) {
if (ep->d_type != DT_REG && ep->d_type != DT_LNK)
continue;
if (!DString::endWith(ep->d_name, ".desktop"))
continue;
m_appNames.insert({ep->d_name, true});
}
closedir(dp);
return m_appNames;
}
// 获取所有应用信息
std::vector<DesktopInfo> AppsDir::getAllDesktopInfos()
{
std::vector<DesktopInfo> desktopInfos;
for (auto dir : BaseDir::appDirs()) {
AppsDir appsDir(dir);
std::map<std::string, bool> appNames = appsDir.getAppNames();
if (appNames.size() == 0)
continue;
for (const auto &iter : appNames) {
std::string filePath = dir + iter.first;
DesktopInfo desktopInfo(filePath);
if (!DFile::isExisted(filePath) || !desktopInfo.isValidDesktop() || !desktopInfo.shouldShow()) {
qDebug() << QString("app item %1 doesn't show in the list..").arg(QString::fromStdString(filePath));
continue;
}
desktopInfos.push_back(desktopInfo);
}
}
return desktopInfos;
}