*修改自启动应用启动方式, 采用loader进程先校验应用信息,再拉起应用的方式 *修复launcher模块部分接口导出失败的问题 *增加launcher、dock、loader模块代码注释 Log: 修改自启动应用启动方式 Task: https://pms.uniontech.com/task-view-131611.html Influence: 应用启动方式不同 Change-Id: Ie88027602d1395c6bbad69e610f3639ecc7dfbc1
1225 lines
36 KiB
C++
1225 lines
36 KiB
C++
/*
|
||
* Copyright (C) 2021 ~ 2022 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)));
|
||
|
||
// 监控应用目录
|
||
watchDataDirs();
|
||
// 关联org.deepin.daemon.LRecorder1接口事件ServiceRestarted
|
||
QDBusConnection::sessionBus().connect("org.deepin.daemon.AlRecorder1",
|
||
"/org/deepin/daemon/AlRecorder1",
|
||
"org.deepin.daemon.AlRecorder1",
|
||
"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.AlRecorder1",
|
||
"/org/deepin/daemon/AlRecorder1",
|
||
"org.deepin.daemon.AlRecorder1",
|
||
"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);
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getAllItemInfos 获取所有应用信息
|
||
* @return
|
||
*/
|
||
LauncherItemInfoList Launcher::getAllItemInfos()
|
||
{
|
||
LauncherItemInfoList ret;
|
||
for (auto item : itemsMap) {
|
||
ret.push_back(item.info);
|
||
}
|
||
return ret;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getAllNewInstalledApps 获取所有新安装且未打开的应用
|
||
* @return
|
||
*/
|
||
QStringList Launcher::getAllNewInstalledApps()
|
||
{
|
||
QStringList ret;
|
||
QMap<QString, QStringList> newApps;
|
||
QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecorder1", "/org/deepin/daemon/AlRecorder1", "org.deepin.daemon.AlRecorder1");
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getDisableScaling 获取应用是否禁用缩放
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getItemInfo 获取应用信息
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
LauncherItemInfo Launcher::getItemInfo(QString appId)
|
||
{
|
||
LauncherItemInfo info;
|
||
if (itemsMap.find(appId) == itemsMap.end())
|
||
return info;
|
||
|
||
info = itemsMap[appId].info;
|
||
return info;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getUseProxy 获取应用是否代理
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::isItemOnDesktop 桌面是否存在应用desktop文件
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::requestRemoveFromDesktop 移除桌面快捷方式
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::requestSendToDesktop 发送应用到桌面
|
||
* @param appId
|
||
* @return
|
||
*/
|
||
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.getKeyFile()->setKey(MainSection, dbusService.toStdString(), "X-Deepin-CreatedBy");
|
||
dinfo.getKeyFile()->setKey(MainSection, appId.toStdString(), "X-Deepin-AppID");
|
||
if (!dinfo.getKeyFile()->saveToFile(filePath.toStdString()))
|
||
return false;
|
||
|
||
std::thread thread([]() {
|
||
// TODO 播放系统音效
|
||
|
||
});
|
||
thread.detach();
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::requestUninstall 卸载应用
|
||
* @param appId
|
||
*/
|
||
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.AlRecorder1", "/org/deepin/daemon/AlRecorder1", "org.deepin.daemon.AlRecorder1");
|
||
interface.call("UninstallHints", QStringList() << 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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::setDisableScaling 设置应用是否禁用缩放
|
||
* @param appId
|
||
* @param value
|
||
*/
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::setUseProxy 设置用户代理
|
||
* @param appId
|
||
* @param value
|
||
*/
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::handleFSWatcherEvents 处理文件改变事件
|
||
* @param msg
|
||
*/
|
||
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 (qint64(ty) != item.info.categoryId) {
|
||
item.info.categoryId = qint64(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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::initSettings 初始化启动器配置
|
||
*/
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::loadDesktopPkgMap 加载应用包信息
|
||
*/
|
||
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();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::loadPkgCategoryMap 加载应用类型信息
|
||
*/
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::handleAppHiddenChanged 响应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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::loadNameMap 加载翻译应用信息
|
||
*/
|
||
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();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::initItems 初始化应用信息
|
||
*/
|
||
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 = qint64(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);
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getXCategory 获取应用类型
|
||
* @param item
|
||
* @return
|
||
*/
|
||
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];
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::queryPkgNameWithDpkg 使用dpkg -S,通过文件路径查包
|
||
* @param itemPath
|
||
* @return
|
||
*/
|
||
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 "";
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::queryPkgName 通过id、path查询包名
|
||
* @param itemID
|
||
* @param itemPath
|
||
* @return
|
||
*/
|
||
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 "";
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::getItemByPath 根据desktop路径获取Item信息
|
||
* @param itemPath
|
||
* @return
|
||
*/
|
||
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();
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::watchDataDirs 监控应用数据目录
|
||
*/
|
||
void Launcher::watchDataDirs()
|
||
{
|
||
QStringList dataDirs;
|
||
dataDirs << QDir::homePath() + ".local/share";
|
||
dataDirs << "/usr/local/share" << "/usr/share";
|
||
QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecorder1", "/org/deepin/daemon/AlRecorder1", "org.deepin.daemon.AlRecorder1");
|
||
interface.call("WatchDirs", dataDirs);
|
||
}
|
||
|
||
void Launcher::emitItemChanged(const Item *item, QString status)
|
||
{
|
||
LauncherItemInfo 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.getKeyFile()->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.getKeyFile()->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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::doUninstall 执行卸载操作
|
||
* @param info
|
||
* @param item
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::uninstallFlatpak 卸载flatpak应用
|
||
* @param info
|
||
* @param item
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::uninstallWineApp 卸载wine应用
|
||
* @param item
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::uninstallSysApp 卸载系统App
|
||
* @param name
|
||
* @param pkg
|
||
* @return
|
||
*/
|
||
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<QDBusObjectPath> reply = lastoreDbus.call("RemovePackage", name, pkg);
|
||
QDBusObjectPath jobPath;
|
||
if (reply.isValid())
|
||
reply.value();
|
||
|
||
if (jobPath.path().isEmpty())
|
||
return false;
|
||
|
||
QEventLoop loop;
|
||
bool ret = false;
|
||
QDBusConnection::sessionBus().connect("com.deepin.lastore",
|
||
jobPath.path(),
|
||
"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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::removeDesktop 移除desktop文件
|
||
* @param item
|
||
* @return
|
||
*/
|
||
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;
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::notifyUninstallDone 发送卸载结果
|
||
* @param item
|
||
* @param result
|
||
*/
|
||
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);
|
||
}
|
||
|
||
/**
|
||
* @brief Launcher::isDeepinCustomDesktopFile 检测是否为Deepin定制应用
|
||
* @param fileName
|
||
* @return
|
||
*/
|
||
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.getKeyFile()->getStr(MainSection, KeyName).c_str());
|
||
QString enComment(info.getKeyFile()->getStr(MainSection, KeyComment).c_str());
|
||
QString xDeepinCategory(info.getKeyFile()->getStr(MainSection, "X-Deepin-Category").c_str());
|
||
QString xDeepinVendor(info.getKeyFile()->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.keywords << enName << appName;
|
||
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.desktopKeywords.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];
|
||
}
|
||
|
||
|
||
|
||
|