1112 lines
33 KiB
C++
1112 lines
33 KiB
C++
![]() |
/*
|
|||
|
* Copyright (C) 2022 ~ 2023 Deepin Technology Co., Ltd.
|
|||
|
*
|
|||
|
* Author: weizhixiang <weizhixiang@uniontech.com>
|
|||
|
*
|
|||
|
* Maintainer: weizhixiang <weizhixiang@uniontech.com>
|
|||
|
*
|
|||
|
* This program is free software: you can redistribute it and/or modify
|
|||
|
* it under the terms of the GNU General Public License as published by
|
|||
|
* the Free Software Foundation, either version 3 of the License, or
|
|||
|
* any later version.
|
|||
|
*
|
|||
|
* This program is distributed in the hope that it will be useful,
|
|||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|||
|
* GNU General Public License for more details.
|
|||
|
*
|
|||
|
* You should have received a copy of the GNU General Public License
|
|||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||
|
*/
|
|||
|
|
|||
|
#include "launcher.h"
|
|||
|
#include "lang.h"
|
|||
|
#include "desktopinfo.h"
|
|||
|
#include "settings.h"
|
|||
|
#include "basedir.h"
|
|||
|
#include "launchersettings.h"
|
|||
|
|
|||
|
#include <QDBusConnection>
|
|||
|
#include <QDBusError>
|
|||
|
#include <QtDebug>
|
|||
|
#include <QJsonDocument>
|
|||
|
#include <QJsonObject>
|
|||
|
#include <QDir>
|
|||
|
#include <QDateTime>
|
|||
|
#include <QProcess>
|
|||
|
#include <QDBusMessage>
|
|||
|
#include <QDBusInterface>
|
|||
|
#include <QDBusConnectionInterface>
|
|||
|
#include <QEventLoop>
|
|||
|
|
|||
|
#include <regex>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <thread>
|
|||
|
|
|||
|
DCORE_USE_NAMESPACE
|
|||
|
|
|||
|
#define SETTING LauncherSettings::instance()
|
|||
|
|
|||
|
Launcher::Launcher(QObject *parent)
|
|||
|
: SynModule(parent)
|
|||
|
{
|
|||
|
registeModule("launcher");
|
|||
|
initSettings();
|
|||
|
|
|||
|
for (auto dir : BaseDir::appDirs()) {
|
|||
|
appDirs.push_back(dir.c_str());
|
|||
|
}
|
|||
|
|
|||
|
loadDesktopPkgMap();
|
|||
|
loadPkgCategoryMap();
|
|||
|
loadNameMap();
|
|||
|
initItems();
|
|||
|
|
|||
|
// 关联org.deepin.daemon.DFWatcher1接口事件Event
|
|||
|
QDBusConnection::sessionBus().connect("org.deepin.daemon.DFWatcher1",
|
|||
|
"/org/deepin/daemon/DFWatcher1",
|
|||
|
"org.deepin.daemon.DFWatcher1",
|
|||
|
"Event", "", // TODO 修正事件参数
|
|||
|
this, SLOT(handleFSWatcherEvents(QDBusMessage)));
|
|||
|
|
|||
|
// 关联org.deepin.daemon.LRecorder1接口事件ServiceRestarted
|
|||
|
watchDataDirs(); // 监控应用目录
|
|||
|
QDBusConnection::sessionBus().connect("org.deepin.daemon.AlRecoder1",
|
|||
|
"/org/deepin/daemon/AlRecoder1",
|
|||
|
"org.deepin.daemon.AlRecoder1",
|
|||
|
"ServiceRestarted",
|
|||
|
this, SLOT(handleLRecoderRestart(QDBusMessage)));
|
|||
|
|
|||
|
|
|||
|
QDBusConnectionInterface *ifc = QDBusConnection::sessionBus().interface();
|
|||
|
connect(ifc, &QDBusConnectionInterface::serviceOwnerChanged, this, [ & ](const QString &name, const QString &oldOwner, const QString &newOwner) {
|
|||
|
Q_UNUSED(name)
|
|||
|
Q_UNUSED(oldOwner)
|
|||
|
Q_UNUSED(newOwner)
|
|||
|
watchDataDirs();
|
|||
|
});
|
|||
|
|
|||
|
// 关联org.deepin.daemon.LRecorder1接口事件Launched
|
|||
|
QDBusConnection::sessionBus().connect("org.deepin.daemon.AlRecoder1",
|
|||
|
"/org/deepin/daemon/AlRecoder1",
|
|||
|
"org.deepin.daemon.AlRecoder1",
|
|||
|
"Launched", "sa",
|
|||
|
this, SLOT([&](QDBusMessage msg) {
|
|||
|
QString path = msg.arguments().at(0).toString();
|
|||
|
Item item = getItemByPath(path);
|
|||
|
if (item.isValid())
|
|||
|
Q_EMIT NewAppLaunched(item.id);
|
|||
|
}));
|
|||
|
}
|
|||
|
|
|||
|
Launcher::~Launcher()
|
|||
|
{
|
|||
|
QDBusConnection::sessionBus().unregisterObject(dbusPath);
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::setSynConfig(QByteArray ba)
|
|||
|
{
|
|||
|
if (!SETTING)
|
|||
|
return;
|
|||
|
|
|||
|
QJsonDocument doc = QJsonDocument::fromJson(ba);
|
|||
|
QJsonObject obj = doc.object();
|
|||
|
SETTING->setDisplayMode(obj["display_mode"].toInt() == 1 ? "category" : "free");
|
|||
|
SETTING->setFullscreenMode(obj["fullscreen"].toBool());
|
|||
|
}
|
|||
|
|
|||
|
QByteArray Launcher::getSyncConfig()
|
|||
|
{
|
|||
|
QJsonObject obj;
|
|||
|
obj["version"] = "1.0";
|
|||
|
obj["display_mode"] = SETTING->getDisplayMode();
|
|||
|
obj["fullscreen"] = SETTING->getFullscreenMode();
|
|||
|
QJsonDocument doc(obj);
|
|||
|
return doc.toJson();
|
|||
|
}
|
|||
|
|
|||
|
const QMap<QString, Item> *Launcher::getItems()
|
|||
|
{
|
|||
|
return &itemsMap;
|
|||
|
}
|
|||
|
|
|||
|
int Launcher::getDisplayMode()
|
|||
|
{
|
|||
|
return SETTING->getDisplayMode() == "category" ? 1 : 0;
|
|||
|
}
|
|||
|
|
|||
|
bool Launcher::getFullScreen()
|
|||
|
{
|
|||
|
return SETTING->getFullscreenMode();
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::setDisplayMode(int value)
|
|||
|
{
|
|||
|
SETTING->setDisplayMode(value == 1 ? "category" : "free");
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::setFullScreen(bool isFull)
|
|||
|
{
|
|||
|
SETTING->setFullscreenMode(isFull);
|
|||
|
}
|
|||
|
|
|||
|
// 获取所有应用信息
|
|||
|
QVector<ItemInfo> Launcher::getAllItemInfos()
|
|||
|
{
|
|||
|
QVector<ItemInfo> ret;
|
|||
|
for (auto item : itemsMap) {
|
|||
|
ret.push_back(item.info);
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
// 获取未打开的应用
|
|||
|
QStringList Launcher::getAllNewInstalledApps()
|
|||
|
{
|
|||
|
QStringList ret;
|
|||
|
QMap<QString, QStringList> newApps;
|
|||
|
QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecoder1", "/org/deepin/daemon/AlRecoder1", "org.deepin.daemon.AlRecoder1");
|
|||
|
QDBusReply<QMap<QString, QStringList>> reply = interface.call("GetNew");
|
|||
|
if (reply.isValid())
|
|||
|
newApps = reply;
|
|||
|
|
|||
|
for (auto iter = newApps.begin(); iter != newApps.end(); iter++) {
|
|||
|
if (iter.value().size() > 0) {
|
|||
|
ret << iter.value();
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
// 获取应用是否缩放
|
|||
|
bool Launcher::getDisableScaling(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return false;
|
|||
|
|
|||
|
for (const auto &app : SETTING->getDisableScalingApps()) {
|
|||
|
if (app == appId)
|
|||
|
return true;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
// 获取应用信息
|
|||
|
ItemInfo Launcher::getItemInfo(QString appId)
|
|||
|
{
|
|||
|
ItemInfo info;
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return info;
|
|||
|
|
|||
|
info = itemsMap[appId].info;
|
|||
|
return info;
|
|||
|
}
|
|||
|
|
|||
|
// 获取应用是否代理
|
|||
|
bool Launcher::getUseProxy(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return false;
|
|||
|
|
|||
|
for (const auto &app : SETTING->getUseProxyApps()) {
|
|||
|
if (app == appId)
|
|||
|
return true;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
// 桌面是否存在应用desktop文件
|
|||
|
bool Launcher::isItemOnDesktop(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return false;
|
|||
|
|
|||
|
QString filePath(QDir::homePath() + "/Desktop/" + appId + ".desktop");
|
|||
|
QFileInfo info(filePath);
|
|||
|
return info.exists();
|
|||
|
}
|
|||
|
|
|||
|
// 移除桌面快捷方式
|
|||
|
bool Launcher::requestRemoveFromDesktop(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return false;
|
|||
|
|
|||
|
QString filePath(QDir::homePath() + "/Desktop/" + appId + ".desktop");
|
|||
|
QFileInfo info(filePath);
|
|||
|
if (!info.exists())
|
|||
|
return true;
|
|||
|
|
|||
|
QFile file(filePath);
|
|||
|
return file.remove();
|
|||
|
}
|
|||
|
|
|||
|
// 发送应用到桌面
|
|||
|
bool Launcher::requestSendToDesktop(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return false;
|
|||
|
|
|||
|
QString filePath(QDir::homePath() + "/Desktop/" + appId + ".desktop");
|
|||
|
QFileInfo info(filePath);
|
|||
|
if (info.exists()) // 已经存在
|
|||
|
return false;
|
|||
|
|
|||
|
// 创建桌面快捷方式文件
|
|||
|
DesktopInfo dinfo(itemsMap[appId].info.path.toStdString());
|
|||
|
dinfo.kf.setKey(MainSection, dbusService.toStdString(), "X-Deepin-CreatedBy");
|
|||
|
dinfo.kf.setKey(MainSection, appId.toStdString(), "X-Deepin-AppID");
|
|||
|
if (!dinfo.kf.saveToFile(filePath.toStdString()))
|
|||
|
return false;
|
|||
|
|
|||
|
std::thread thread([]() {
|
|||
|
// TODO 播放系统音效
|
|||
|
|
|||
|
});
|
|||
|
thread.detach();
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
// 卸载应用
|
|||
|
void Launcher::requestUninstall(QString appId)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return;
|
|||
|
|
|||
|
// 限制调用方
|
|||
|
QString cmd = QString("cat /proc/%1/cmdline").arg(QString::number(QDBusConnection::sessionBus().interface()->servicePid(message().service())));
|
|||
|
QProcess process;
|
|||
|
QStringList args {"-c", cmd};
|
|||
|
process.start("sh", args);
|
|||
|
process.waitForReadyRead();
|
|||
|
QString result = QString::fromUtf8(process.readAllStandardOutput());
|
|||
|
qInfo() << "RequestUninstall fucntion called by :" << result;
|
|||
|
process.close();
|
|||
|
if (result == launcherExe) {
|
|||
|
qWarning() << result << " has no right to uninstall " << appId;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 执行卸载动作
|
|||
|
std::thread thread([&](QString appId) {
|
|||
|
const auto &item = itemsMap[appId];
|
|||
|
DesktopInfo info(item.info.path.toStdString());
|
|||
|
if (!info.isValidDesktop())
|
|||
|
return;
|
|||
|
|
|||
|
// 即将卸载appId
|
|||
|
QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecoder1", "/org/deepin/daemon/AlRecoder1", "org.deepin.daemon.AlRecoder1");
|
|||
|
interface.call("UninstallHints", item.info.path);
|
|||
|
|
|||
|
bool ret = doUninstall(info, item); // 阻塞等待
|
|||
|
if (!ret) {
|
|||
|
QString msg = QString("uninstall %1 result %2").arg(info.getName().c_str()).arg(ret);
|
|||
|
Q_EMIT UninstallFailed(appId, msg);
|
|||
|
qInfo() << msg;
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// 从自动启动目录中移除
|
|||
|
QString filePath(QDir::homePath() + "/.config/autostart/" + appId + ".desktop");
|
|||
|
QFile file(filePath);
|
|||
|
file.remove();
|
|||
|
Q_EMIT UninstallSuccess(appId);
|
|||
|
}, appId);
|
|||
|
thread.detach();
|
|||
|
}
|
|||
|
|
|||
|
// 设置应用禁用缩放
|
|||
|
void Launcher::setDisableScaling(QString appId, bool value)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return;
|
|||
|
|
|||
|
QVector<QString> apps = SETTING->getDisableScalingApps();
|
|||
|
if (value) {
|
|||
|
for (const auto &app : apps) {
|
|||
|
if (app == appId) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
apps.append(appId);
|
|||
|
} else {
|
|||
|
bool found = false;
|
|||
|
for (auto iter = apps.begin(); iter != apps.end(); iter++) {
|
|||
|
if (*iter == appId) {
|
|||
|
found = true;
|
|||
|
apps.erase(iter);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!found)
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
SETTING->setDisableScalingApps(apps);
|
|||
|
}
|
|||
|
|
|||
|
// 设置用户代理
|
|||
|
void Launcher::setUseProxy(QString appId, bool value)
|
|||
|
{
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return;
|
|||
|
|
|||
|
|
|||
|
QVector<QString> apps = SETTING->getUseProxyApps();
|
|||
|
if (value) {
|
|||
|
for (const auto &app : apps) {
|
|||
|
if (app == appId) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
apps.append(appId);
|
|||
|
} else {
|
|||
|
bool found = false;
|
|||
|
for (auto iter = apps.begin(); iter != apps.end(); iter++) {
|
|||
|
if (*iter == appId) {
|
|||
|
found = true;
|
|||
|
apps.erase(iter);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!found)
|
|||
|
return;
|
|||
|
}
|
|||
|
SETTING->setUseProxy(apps);
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::handleFSWatcherEvents(QDBusMessage msg)
|
|||
|
{
|
|||
|
QList<QVariant> ret = msg.arguments();
|
|||
|
if (ret.size() != 2)
|
|||
|
return;
|
|||
|
|
|||
|
QString filePath = ret[0].toString();
|
|||
|
int op = ret[0].toInt();
|
|||
|
|
|||
|
// desktop包文件变化
|
|||
|
if (filePath == desktopPkgMapFile) {
|
|||
|
loadDesktopPkgMap();
|
|||
|
|
|||
|
// retry queryPkgName for m.noPkgItemIDs
|
|||
|
for (auto iter = noPkgItemIds.begin(); iter != noPkgItemIds.end(); iter++) {
|
|||
|
QString id = iter.key();
|
|||
|
QString pkg = queryPkgName(id, "");
|
|||
|
if (pkg.isEmpty())
|
|||
|
continue;
|
|||
|
|
|||
|
Item &item = itemsMap[id];
|
|||
|
Categorytype ty = queryCategoryId(&item);
|
|||
|
if (ty != item.info.categoryId) {
|
|||
|
item.info.categoryId = ty;
|
|||
|
emitItemChanged(&item, appStatusModified);
|
|||
|
}
|
|||
|
noPkgItemIds.remove(id);
|
|||
|
}
|
|||
|
} else if (filePath == applicationsFile) { // 应用信息文件变化
|
|||
|
loadPkgCategoryMap();
|
|||
|
} else if (filePath.endsWith(".desktop")){ // desktop文件变化
|
|||
|
checkDesktopFile(filePath);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void Launcher:: handleLRecoderRestart(QDBusMessage msg)
|
|||
|
{
|
|||
|
Q_UNUSED(msg)
|
|||
|
watchDataDirs();
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::initSettings()
|
|||
|
{
|
|||
|
connect(SETTING, &LauncherSettings::displayModeChanged, this, [&](QString mode) {
|
|||
|
Q_EMIT displayModeChanged(mode == "category" ? 1 : 0);
|
|||
|
});
|
|||
|
connect(SETTING, &LauncherSettings::fullscreenChanged, this, &Launcher::fullScreenChanged);
|
|||
|
connect(SETTING, &LauncherSettings::hiddenAppsChanged, this, &Launcher::handleAppHiddenChanged);
|
|||
|
|
|||
|
appsHidden = SETTING->getHiddenApps();
|
|||
|
}
|
|||
|
|
|||
|
// 加载应用包信息
|
|||
|
void Launcher::loadDesktopPkgMap()
|
|||
|
{
|
|||
|
QFile file(desktopPkgMapFile);
|
|||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|||
|
return;
|
|||
|
|
|||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|||
|
file.close();
|
|||
|
if (!doc.isObject())
|
|||
|
return;
|
|||
|
|
|||
|
QJsonObject obj = doc.object();
|
|||
|
QVariantMap varMap = obj.toVariantMap();
|
|||
|
desktopPkgMap.clear();
|
|||
|
for (auto iter = varMap.begin(); iter != varMap.end(); iter++) {
|
|||
|
if (!QDir::isAbsolutePath(iter.key()))
|
|||
|
continue;
|
|||
|
|
|||
|
QString appId = getAppIdByFilePath(iter.key(), appDirs);
|
|||
|
if (appId == "")
|
|||
|
continue;
|
|||
|
|
|||
|
desktopPkgMap[appId] = iter.value().toString();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 加载应用类型信息
|
|||
|
void Launcher::loadPkgCategoryMap()
|
|||
|
{
|
|||
|
QFile file(applicationsFile);
|
|||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|||
|
return;
|
|||
|
|
|||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|||
|
file.close();
|
|||
|
if (!doc.isObject())
|
|||
|
return;
|
|||
|
|
|||
|
QJsonObject obj = doc.object();
|
|||
|
QVariantMap varMap = obj.toVariantMap();
|
|||
|
pkgCategoryMap.clear();
|
|||
|
for (auto iter = varMap.begin(); iter != varMap.end(); iter++) {
|
|||
|
if (!iter.value().toJsonValue().isObject())
|
|||
|
continue;
|
|||
|
|
|||
|
QJsonObject infoObj = iter.value().toJsonObject();
|
|||
|
QVariantMap infoMap = infoObj.toVariantMap();
|
|||
|
QString category = infoMap["category"].toString();
|
|||
|
pkgCategoryMap[iter.key()] = Category::parseCategoryString(category);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::checkDesktopFile(QString filePath)
|
|||
|
{
|
|||
|
QString appId = getAppIdByFilePath(filePath, appDirs);
|
|||
|
if (appId.isEmpty())
|
|||
|
return;
|
|||
|
|
|||
|
DesktopInfo info(filePath.toStdString());
|
|||
|
if (info.isValidDesktop()) {
|
|||
|
Item newItem = NewItemWithDesktopInfo(info);
|
|||
|
newItem.info.id = getAppIdByFilePath(newItem.info.path, appDirs);
|
|||
|
bool shouldShow = info.shouldShow() &&
|
|||
|
!isDeepinCustomDesktopFile(newItem.info.path) &&
|
|||
|
!appsHidden.contains(newItem.info.id);
|
|||
|
|
|||
|
// update item
|
|||
|
if (itemsMap.find(appId) != itemsMap.end()) {
|
|||
|
if (shouldShow) {
|
|||
|
addItem(newItem);
|
|||
|
emitItemChanged(&newItem, appStatusModified);
|
|||
|
} else {
|
|||
|
itemsMap.remove(appId);
|
|||
|
emitItemChanged(&newItem, appStatusDeleted);
|
|||
|
}
|
|||
|
} else if (shouldShow){ // add item
|
|||
|
if (info.isExecutableOk()) {
|
|||
|
addItem(newItem);
|
|||
|
emitItemChanged(&newItem, appStatusCreated);
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (itemsMap.find(appId) != itemsMap.end()) {
|
|||
|
Item item = itemsMap[appId];
|
|||
|
emitItemChanged(&item, appStatusDeleted);
|
|||
|
itemsMap.remove(appId);
|
|||
|
|
|||
|
// 移除桌面上对应文件
|
|||
|
QFile file(QDir::homePath() + "/Desktop" + appId + ".desktop");
|
|||
|
file.remove();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 响应DConfig中隐藏应用配置变化
|
|||
|
void Launcher::handleAppHiddenChanged()
|
|||
|
{
|
|||
|
auto hiddenApps = SETTING->getHiddenApps();
|
|||
|
QSet<QString> newSet, oldSet;
|
|||
|
for (const auto &app : hiddenApps)
|
|||
|
newSet.insert(app);
|
|||
|
|
|||
|
for (const auto &app : appsHidden)
|
|||
|
oldSet.insert(app);
|
|||
|
|
|||
|
// 处理新增隐藏应用
|
|||
|
for (const auto &app : newSet - oldSet) {
|
|||
|
if (itemsMap.find(app) == itemsMap.end())
|
|||
|
continue;
|
|||
|
|
|||
|
emitItemChanged(&itemsMap[app], appStatusDeleted);
|
|||
|
itemsMap.remove(app);
|
|||
|
}
|
|||
|
|
|||
|
// 处理显示应用
|
|||
|
for (const auto &appId : oldSet - newSet) {
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
continue;
|
|||
|
|
|||
|
DesktopInfo info = DesktopInfo(appId.toStdString());
|
|||
|
if (!info.isValidDesktop()) {
|
|||
|
qInfo() << "appId " << appId << "is invalid app";
|
|||
|
}
|
|||
|
|
|||
|
Item item = NewItemWithDesktopInfo(info);
|
|||
|
item.info.id = getAppIdByFilePath(item.info.path, appDirs);
|
|||
|
|
|||
|
if (!(info.shouldShow() && !isDeepinCustomDesktopFile(info.getFileName().c_str())))
|
|||
|
continue;
|
|||
|
|
|||
|
addItem(item);
|
|||
|
emitItemChanged(&item, appStatusCreated);
|
|||
|
}
|
|||
|
|
|||
|
appsHidden.clear();
|
|||
|
for (const auto &appId : hiddenApps) {
|
|||
|
appsHidden.push_back(appId);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 加载翻译应用信息
|
|||
|
void Launcher::loadNameMap()
|
|||
|
{
|
|||
|
QFile file(appNameTranslationsFile);
|
|||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|||
|
return;
|
|||
|
|
|||
|
QJsonDocument doc = QJsonDocument::fromJson(file.readAll());
|
|||
|
file.close();
|
|||
|
if (!doc.isObject())
|
|||
|
return;
|
|||
|
|
|||
|
QJsonObject obj = doc.object();
|
|||
|
QVariantMap varMap = obj.toVariantMap();
|
|||
|
nameMap.clear();
|
|||
|
|
|||
|
QString lang(queryLangs()[0].data());
|
|||
|
for (auto iter = varMap.begin(); iter != varMap.end(); iter++) {
|
|||
|
// 过滤非Object
|
|||
|
if (!iter.value().toJsonValue().isObject())
|
|||
|
continue;
|
|||
|
|
|||
|
// 过滤非本地语言
|
|||
|
if (iter.key() != lang)
|
|||
|
continue;
|
|||
|
|
|||
|
QJsonObject infoObj = iter.value().toJsonObject();
|
|||
|
QVariantMap infoMap = infoObj.toVariantMap();
|
|||
|
for (auto infoIter = infoMap.begin(); infoIter != infoMap.end(); infoIter++) {
|
|||
|
// 过滤Object
|
|||
|
if (infoIter.value().toJsonValue().isObject())
|
|||
|
continue;
|
|||
|
|
|||
|
nameMap[infoIter.key()] = infoIter.value().toString();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 初始化应用信息
|
|||
|
void Launcher::initItems()
|
|||
|
{
|
|||
|
std::vector<DesktopInfo> infos = AppsDir::getAllDesktopInfos();
|
|||
|
for (auto &app : infos) {
|
|||
|
if (!app.isExecutableOk()
|
|||
|
|| app.getId().empty()
|
|||
|
|| isDeepinCustomDesktopFile(app.getFileName().c_str()))
|
|||
|
{
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
Item item = NewItemWithDesktopInfo(app);
|
|||
|
if (appsHidden.contains(item.info.id))
|
|||
|
continue;
|
|||
|
|
|||
|
addItem(item);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::addItem(Item item)
|
|||
|
{
|
|||
|
if (!item.isValid())
|
|||
|
return;
|
|||
|
|
|||
|
if (nameMap.size() > 0 && nameMap.find(item.info.id) != nameMap.end()) {
|
|||
|
QString name = nameMap[item.info.id];
|
|||
|
if (!name.isEmpty())
|
|||
|
item.info.name = name;
|
|||
|
}
|
|||
|
|
|||
|
item.info.categoryId = queryCategoryId(&item);
|
|||
|
itemsMap[item.info.id] = item;
|
|||
|
}
|
|||
|
|
|||
|
Categorytype Launcher::queryCategoryId(const Item *item)
|
|||
|
{
|
|||
|
QString pkg = queryPkgName(item->info.id, item->info.path);
|
|||
|
if (pkg.isEmpty()) {
|
|||
|
noPkgItemIds[item->info.id] = 1;
|
|||
|
|
|||
|
if (pkgCategoryMap.find(pkg) != pkgCategoryMap.end())
|
|||
|
return pkgCategoryMap[pkg];
|
|||
|
}
|
|||
|
|
|||
|
Categorytype category = Category::parseCategoryString(item->xDeepinCategory);
|
|||
|
if (category != Categorytype::CategoryErr)
|
|||
|
return category;
|
|||
|
|
|||
|
|
|||
|
return getXCategory(item);
|
|||
|
}
|
|||
|
|
|||
|
// 获取应用类型
|
|||
|
Categorytype Launcher::getXCategory(const Item *item)
|
|||
|
{
|
|||
|
// 统计应用类型
|
|||
|
QMap<Categorytype, int> categoriesMap;
|
|||
|
for (const auto &category : item->categories) {
|
|||
|
QString lCategory = category.toLower();
|
|||
|
QList<Categorytype> tys;
|
|||
|
Categorytype ty = Category::parseCategoryString(lCategory);
|
|||
|
if (ty != Categorytype::CategoryErr)
|
|||
|
tys.push_back(ty);
|
|||
|
else if (Category::parseXCategoryString(lCategory).size() > 0) {
|
|||
|
tys.append(Category::parseXCategoryString(lCategory));
|
|||
|
}
|
|||
|
|
|||
|
for (const auto & ty : tys)
|
|||
|
categoriesMap[ty]++;
|
|||
|
}
|
|||
|
|
|||
|
categoriesMap.remove(Categorytype::CategoryOthers);
|
|||
|
if (categoriesMap.size() == 0)
|
|||
|
return Categorytype::CategoryOthers;
|
|||
|
|
|||
|
// 计算最多的类型
|
|||
|
int max = 0;
|
|||
|
Categorytype ty {Categorytype::CategoryOthers};
|
|||
|
for (auto iter = categoriesMap.begin(); iter != categoriesMap.end(); iter++) {
|
|||
|
if (iter.value() > max) {
|
|||
|
max = iter.value();
|
|||
|
ty = iter.key();
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
QList<Categorytype> maxCatogories {ty};
|
|||
|
for (auto iter = categoriesMap.begin(); iter != categoriesMap.end(); iter++) {
|
|||
|
if (iter.key() != ty && iter.value() == max) {
|
|||
|
maxCatogories.push_back(iter.key());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (maxCatogories.size() == 1)
|
|||
|
return maxCatogories[0];
|
|||
|
|
|||
|
qSort(maxCatogories.begin(), maxCatogories.end());
|
|||
|
|
|||
|
// 检查是否同时存在音乐和视频播放器
|
|||
|
QPair<bool, bool> found;
|
|||
|
for (auto iter = maxCatogories.begin(); iter != maxCatogories.end(); iter++) {
|
|||
|
if (*iter == Categorytype::CategoryMusic)
|
|||
|
found.first = true;
|
|||
|
else if (*iter == Categorytype::CategoryVideo)
|
|||
|
found.second = true;
|
|||
|
}
|
|||
|
|
|||
|
if (found.first && found.second)
|
|||
|
return Categorytype::CategoryVideo;
|
|||
|
|
|||
|
return maxCatogories[0];
|
|||
|
}
|
|||
|
|
|||
|
// 使用dpkg -S,通过文件路径查包
|
|||
|
QString Launcher::queryPkgNameWithDpkg(const QString &itemPath)
|
|||
|
{
|
|||
|
QProcess process;
|
|||
|
process.start("dpkg -S " + itemPath);
|
|||
|
if (!process.waitForFinished())
|
|||
|
return "";
|
|||
|
|
|||
|
QByteArray output = process.readAllStandardOutput();
|
|||
|
if (output.size() == 0)
|
|||
|
return "";
|
|||
|
|
|||
|
std::vector<std::string> splits = DString::splitChars(output.data(), '\n');
|
|||
|
if (splits.size() > 0) {
|
|||
|
std::vector<std::string> parts = DString::splitStr(splits[0], ':');
|
|||
|
if (parts.size() == 2)
|
|||
|
return parts[0].c_str();
|
|||
|
}
|
|||
|
return "";
|
|||
|
}
|
|||
|
|
|||
|
// 通过id、path查询包名
|
|||
|
QString Launcher::queryPkgName(const QString &itemID, const QString &itemPath)
|
|||
|
{
|
|||
|
if (!itemPath.isEmpty()) {
|
|||
|
QFileInfo itemInfo(itemPath);
|
|||
|
if (!itemInfo.isFile())
|
|||
|
return "";
|
|||
|
|
|||
|
// 处理desktop文件是软连接的情况
|
|||
|
if (itemInfo.isSymLink()) {
|
|||
|
std::string path = itemInfo.symLinkTarget().toStdString();
|
|||
|
std::smatch result;
|
|||
|
const std::regex e("^/opt/apps/([^/]+)/entries/applications/.*");
|
|||
|
if (std::regex_match(path, result, e) && result.size() == 2) {
|
|||
|
// dpkg命令检查通过路径匹配的包是否存在
|
|||
|
QString pkgName(result[1].str().c_str());
|
|||
|
QProcess process;
|
|||
|
process.start("dpkg -s " + pkgName);
|
|||
|
if (process.waitForFinished())
|
|||
|
return pkgName;
|
|||
|
|
|||
|
// 当包不存在则使用dpkg -S来查找包
|
|||
|
process.start("dpkg -S" + pkgName);
|
|||
|
if (!process.waitForFinished())
|
|||
|
return "";
|
|||
|
|
|||
|
QByteArray output = process.readAllStandardOutput();
|
|||
|
if (output.size() == 0)
|
|||
|
return "";
|
|||
|
|
|||
|
std::vector<std::string> splits = DString::splitChars(output.data(), ':');
|
|||
|
if (splits.size() < 2)
|
|||
|
return "";
|
|||
|
|
|||
|
return splits[0].c_str();
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (DString::startWith(itemID.toStdString(), "org.deepin.flatdeb."))
|
|||
|
return "deepin-fpapp-" + itemID;
|
|||
|
|
|||
|
if (desktopPkgMap.find(itemID) != desktopPkgMap.end())
|
|||
|
return desktopPkgMap[itemID];
|
|||
|
|
|||
|
return "";
|
|||
|
}
|
|||
|
|
|||
|
// 根据desktop路径获取Item信息
|
|||
|
Item Launcher::getItemByPath(QString itemPath)
|
|||
|
{
|
|||
|
QString appId = getAppIdByFilePath(itemPath, appDirs);
|
|||
|
if (itemsMap.find(appId) == itemsMap.end())
|
|||
|
return Item();
|
|||
|
|
|||
|
if (itemsMap[appId].info.path == itemPath)
|
|||
|
return itemsMap[appId];
|
|||
|
|
|||
|
return Item();
|
|||
|
}
|
|||
|
|
|||
|
// 监控应用数据目录
|
|||
|
void Launcher::watchDataDirs()
|
|||
|
{
|
|||
|
QStringList dataDirs;
|
|||
|
dataDirs << QDir::homePath() + ".local/share";
|
|||
|
dataDirs << "/usr/local/share" << "/usr/share";
|
|||
|
QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecoder1", "/org/deepin/daemon/AlRecoder1", "org.deepin.daemon.AlRecoder1");
|
|||
|
interface.call("WatchDirs", dataDirs);
|
|||
|
}
|
|||
|
|
|||
|
void Launcher::emitItemChanged(const Item *item, QString status)
|
|||
|
{
|
|||
|
ItemInfo info(item->info);
|
|||
|
Q_EMIT ItemChanged(status, info, info.categoryId);
|
|||
|
}
|
|||
|
|
|||
|
AppType Launcher::getAppType(DesktopInfo &info, const Item &item)
|
|||
|
{
|
|||
|
AppType ty = AppType::Default;
|
|||
|
QFileInfo fileInfo;
|
|||
|
// 判断是否为flatpak应用
|
|||
|
do {
|
|||
|
if (!info.kf.containKey(MainSection, "X-Flatpak"))
|
|||
|
break;
|
|||
|
|
|||
|
std::vector<std::string> parts = DString::splitStr(info.getCommandLine(), ' ');
|
|||
|
if (parts.size() == 0)
|
|||
|
break;
|
|||
|
|
|||
|
fileInfo.setFile(parts[0].c_str());
|
|||
|
if (flatpakBin != fileInfo.baseName())
|
|||
|
break;
|
|||
|
|
|||
|
ty = AppType::Flatpak;
|
|||
|
goto end;
|
|||
|
} while (0);
|
|||
|
|
|||
|
// 判断是否为ChromeShortcut
|
|||
|
do {
|
|||
|
if (!DString::startWith(item.info.id.toStdString(), "chrome-"))
|
|||
|
break;
|
|||
|
|
|||
|
if (!std::regex_match(item.exec.toStdString(), std::regex("google-chrome.*--app-id=")))
|
|||
|
break;
|
|||
|
|
|||
|
ty = AppType::ChromeShortcut;
|
|||
|
goto end;
|
|||
|
} while (0);
|
|||
|
|
|||
|
// 判断是否为CrossOver
|
|||
|
do {
|
|||
|
fileInfo.setFile(info.getExecutable().c_str());
|
|||
|
QString execBase(fileInfo.baseName());
|
|||
|
if (execBase.contains("CrossOver") && (execBase.contains("crossover") || execBase.contains("cxuninstall"))) {
|
|||
|
ty = AppType::CrossOver;
|
|||
|
goto end;
|
|||
|
}
|
|||
|
} while (0);
|
|||
|
|
|||
|
// 判断是否为wineApp
|
|||
|
do {
|
|||
|
std::string createdBy = info.kf.getStr(MainSection, "X-Created-By");
|
|||
|
if (DString::startWith(createdBy, "cxoffice-") || strstr(info.getCommandLine().c_str(), "env WINEPREFIX=")) {
|
|||
|
ty = AppType::WineApp;
|
|||
|
goto end;
|
|||
|
}
|
|||
|
} while (0);
|
|||
|
|
|||
|
|
|||
|
end:
|
|||
|
return ty;
|
|||
|
}
|
|||
|
|
|||
|
bool Launcher::doUninstall(DesktopInfo &info, const Item &item)
|
|||
|
{
|
|||
|
bool ret = false;
|
|||
|
// 查询包名
|
|||
|
QString pkg = queryPkgName(item.info.id, item.info.path);
|
|||
|
if (pkg.isEmpty())
|
|||
|
pkg = queryPkgNameWithDpkg(item.info.path);
|
|||
|
|
|||
|
if (!pkg.isEmpty()) {
|
|||
|
// 检测包是否安装
|
|||
|
QDBusInterface lastoreDbus = QDBusInterface("com.deepin.lastore", "/com/deepin/lastore", "com.deepin.lastore.Manager", QDBusConnection::systemBus());
|
|||
|
QDBusReply<bool> reply = lastoreDbus.call("PackageExists", pkg);
|
|||
|
if (!(reply.isValid() && reply.value())) // 包未安装
|
|||
|
return false;
|
|||
|
else
|
|||
|
return uninstallSysApp(item.info.name, pkg); // 卸载系统应用
|
|||
|
}
|
|||
|
|
|||
|
switch (getAppType(info, item)) {
|
|||
|
case (AppType::Flatpak):
|
|||
|
ret = uninstallFlatpak(info, item); // 待测
|
|||
|
break;
|
|||
|
case (AppType::ChromeShortcut):
|
|||
|
ret = removeDesktop(item);
|
|||
|
break;
|
|||
|
case (AppType::CrossOver):
|
|||
|
ret = uninstallSysApp(item.info.name, "crossvoer"); // 待测
|
|||
|
break;
|
|||
|
case (AppType::WineApp):
|
|||
|
ret = uninstallWineApp(item); // 待测
|
|||
|
break;
|
|||
|
case (AppType::Default):
|
|||
|
ret = removeDesktop(item);
|
|||
|
break;
|
|||
|
default:
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
// 卸载flatpak应用
|
|||
|
bool Launcher::uninstallFlatpak(DesktopInfo &info, const Item &item)
|
|||
|
{
|
|||
|
struct FlatpakApp {
|
|||
|
std::string name;
|
|||
|
std::string arch;
|
|||
|
std::string branch;
|
|||
|
} flat;
|
|||
|
std::vector<std::string> parts = DString::splitStr(info.getCommandLine(), ' ');
|
|||
|
for (unsigned long idx = 0; idx < parts.size(); idx++) {
|
|||
|
if (flat.branch.empty() && DString::startWith(parts[idx], "--branch="))
|
|||
|
flat.branch.assign(parts[idx].c_str() + strlen("--branch="));
|
|||
|
|
|||
|
if (flat.arch.empty() && DString::startWith(parts[idx], "--arch="))
|
|||
|
flat.arch.assign(parts[idx].c_str() + strlen("--arch="));
|
|||
|
|
|||
|
if (flat.name.empty() && idx != 0 && !DString::startWith(parts[idx], "--") && strstr(parts[idx].c_str(), "."))
|
|||
|
flat.name.assign(parts[idx]);
|
|||
|
|
|||
|
if (!flat.branch.empty() && !flat.arch.empty() && !flat.name.empty())
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (flat.branch.empty() || flat.arch.empty() || flat.name.empty()) {
|
|||
|
qInfo() << "uninstall Flatpak failed";
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
bool userApp = item.info.path.startsWith(QDir::homePath());
|
|||
|
QString sysOrUser = "--user";
|
|||
|
if (!userApp) {
|
|||
|
sysOrUser = "--system";
|
|||
|
QString pkgFile = QString("/usr/share/deepin-flatpak/app/%1/%2/%3/pkg").arg(flat.name.c_str()).arg(flat.arch.c_str()).arg(flat.branch.c_str());
|
|||
|
QFile file(pkgFile);
|
|||
|
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
|||
|
return false;
|
|||
|
|
|||
|
QString content(file.readAll());
|
|||
|
QString pkgName = content.trimmed();
|
|||
|
return uninstallSysApp(item.info.name, pkgName);
|
|||
|
} else {
|
|||
|
QString ref = QString("app/%1/%2/%3").arg(flat.name.c_str()).arg(flat.arch.c_str()).arg(flat.branch.c_str());
|
|||
|
qInfo() << "uninstall flatpak ref= " << ref;
|
|||
|
QProcess process;
|
|||
|
process.start("flatpak " + sysOrUser + " uninstall " + ref);
|
|||
|
bool res = process.waitForFinished();
|
|||
|
std::thread thread([&] {
|
|||
|
notifyUninstallDone(item, res);
|
|||
|
});
|
|||
|
thread.detach();
|
|||
|
}
|
|||
|
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
// 卸载wine应用
|
|||
|
bool Launcher::uninstallWineApp(const Item &item)
|
|||
|
{
|
|||
|
QProcess process;
|
|||
|
process.start("/opt/deepinwine/tools/uninstall.sh" + item.info.path);
|
|||
|
bool res = process.waitForFinished();
|
|||
|
std::thread thread([&] {
|
|||
|
notifyUninstallDone(item, res);
|
|||
|
});
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
// 卸载
|
|||
|
bool Launcher::uninstallSysApp(const QString &name, const QString &pkg)
|
|||
|
{
|
|||
|
QDBusInterface lastoreDbus = QDBusInterface("com.deepin.lastore", "/com/deepin/lastore", "com.deepin.lastore.Manager", QDBusConnection::systemBus());
|
|||
|
QDBusReply<QString> reply = lastoreDbus.call("RemovePackage", name, pkg); // TODO replay为空?
|
|||
|
QString jobPath = reply.isValid() ? reply.value() : "";
|
|||
|
if (jobPath.isEmpty())
|
|||
|
return false;
|
|||
|
|
|||
|
QEventLoop loop;
|
|||
|
bool ret = false;
|
|||
|
QDBusConnection::sessionBus().connect("com.deepin.lastore",
|
|||
|
jobPath,
|
|||
|
"com.deepin.lastore.Job",
|
|||
|
"Status",
|
|||
|
"sa",
|
|||
|
this,
|
|||
|
SLOT([&](QDBusMessage msg) {
|
|||
|
QString status = msg.arguments().at(0).toString();
|
|||
|
if (status == "succeed" || status == "end")
|
|||
|
ret = true;
|
|||
|
else if (status == "failed")
|
|||
|
ret = false;
|
|||
|
|
|||
|
loop.quit();
|
|||
|
}));
|
|||
|
loop.exec(); // 阻塞等待任务结束
|
|||
|
|
|||
|
qInfo() << "uninstall app " << name << "result is " << ret;
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
// 移除desktop文件
|
|||
|
bool Launcher::removeDesktop(const Item &item)
|
|||
|
{
|
|||
|
// 移除desktop文件
|
|||
|
QFile file(item.info.path);
|
|||
|
bool ret = file.remove();
|
|||
|
std::thread thread([&] {
|
|||
|
notifyUninstallDone(item, ret);
|
|||
|
});
|
|||
|
thread.detach();
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
// 发送卸载结果
|
|||
|
void Launcher::notifyUninstallDone(const Item &item, bool result)
|
|||
|
{
|
|||
|
QString msg;
|
|||
|
if (result)
|
|||
|
msg = QString("%1 removed successfully").arg(item.info.name);
|
|||
|
else
|
|||
|
msg = QString("Failed to uninstall %1").arg(item.info.name);
|
|||
|
|
|||
|
QDBusInterface interface = QDBusInterface("org.freedesktop.Notifications", "/org/freedesktop/Notifications", "org.freedesktop.Notifications");
|
|||
|
interface.call("Notify", "deepin-app-store", 0, "deepin-appstore", msg, "", QVariant(), QVariant(), -1);
|
|||
|
}
|
|||
|
|
|||
|
// deepin custom app
|
|||
|
bool Launcher::isDeepinCustomDesktopFile(QString fileName)
|
|||
|
{
|
|||
|
QFileInfo fileInfo(fileName);
|
|||
|
return fileInfo.dir() == QDir::homePath() + ".local/share/applications"
|
|||
|
&& fileInfo.baseName().startsWith("deepin-custom-");
|
|||
|
}
|
|||
|
|
|||
|
Item Launcher:: NewItemWithDesktopInfo(DesktopInfo &info)
|
|||
|
{
|
|||
|
QString enName(info.kf.getStr(MainSection, KeyName).c_str());
|
|||
|
QString enComment(info.kf.getStr(MainSection, KeyComment).c_str());
|
|||
|
QString xDeepinCategory(info.kf.getStr(MainSection, "X-Deepin-Category").c_str());
|
|||
|
QString xDeepinVendor(info.kf.getStr(MainSection, "X-Deepin-Vendor").c_str());
|
|||
|
|
|||
|
QString appName;
|
|||
|
if (xDeepinVendor == "deepin")
|
|||
|
appName = info.getGenericName().c_str();
|
|||
|
|
|||
|
if (appName.isEmpty())
|
|||
|
appName = info.getName().c_str();
|
|||
|
|
|||
|
if (appName.isEmpty())
|
|||
|
appName = info.getId().c_str();
|
|||
|
|
|||
|
QString appFileName(info.getFileName().c_str());
|
|||
|
QFileInfo fileInfo(appFileName);
|
|||
|
int64_t ctime = fileInfo.birthTime().toSecsSinceEpoch();
|
|||
|
|
|||
|
Item item;
|
|||
|
item.info.path = appFileName;
|
|||
|
item.info.name = appName;
|
|||
|
item.info.enName = enName;
|
|||
|
item.info.id = getAppIdByFilePath(item.info.path, appDirs);
|
|||
|
item.info.timeInstalled = ctime;
|
|||
|
item.exec = info.getCommandLine().c_str();
|
|||
|
item.genericName = info.getGenericName().c_str();
|
|||
|
item.comment = enComment;
|
|||
|
xDeepinCategory = xDeepinCategory.toLower();
|
|||
|
|
|||
|
for (auto &keyWord : info.getKeywords()) {
|
|||
|
item.keywords.push_back(QString(keyWord.c_str()).toLower());
|
|||
|
}
|
|||
|
|
|||
|
for (auto &category : info.getCategories()) {
|
|||
|
item.categories.push_back(QString(category.c_str()).toLower());
|
|||
|
}
|
|||
|
return item;
|
|||
|
}
|
|||
|
|
|||
|
QString Launcher::getAppIdByFilePath(QString filePath, QStringList dirs)
|
|||
|
{
|
|||
|
QString path = QDir::cleanPath(filePath);
|
|||
|
QString appId;
|
|||
|
for (auto dir : dirs) {
|
|||
|
if (path.startsWith(dir)) {
|
|||
|
appId = QDir(dir).relativeFilePath(path);
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return appId.isEmpty() ? "" : appId.split(".")[0];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|