/* * Copyright (C) 2021 ~ 2022 Deepin Technology Co., Ltd. * * Author: weizhixiang * * Maintainer: weizhixiang * * 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 . */ #include "launcher.h" #include "lang.h" #include "desktopinfo.h" #include "settings.h" #include "basedir.h" #include "launchersettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 *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 Launcher::getAllItemInfos() { QVector ret; for (auto item : itemsMap) { ret.push_back(item.info); } return ret; } // 获取未打开的应用 QStringList Launcher::getAllNewInstalledApps() { QStringList ret; QMap newApps; QDBusInterface interface = QDBusInterface("org.deepin.daemon.AlRecoder1", "/org/deepin/daemon/AlRecoder1", "org.deepin.daemon.AlRecoder1"); QDBusReply> 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.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; } // 卸载应用 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 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 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 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 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 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 categoriesMap; for (const auto &category : item->categories) { QString lCategory = category.toLower(); QList 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 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 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 splits = DString::splitChars(output.data(), '\n'); if (splits.size() > 0) { std::vector 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 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.getKeyFile()->containKey(MainSection, "X-Flatpak")) break; std::vector 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; } 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 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 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 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.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.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]; }