diff --git a/CMakeLists.txt b/CMakeLists.txt index b8131c3..dc1afb1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,18 @@ CPMAddPackage( find_package(PolkitQt6-1 REQUIRED) +# Optional: localized application summaries from the distribution's AppStream +# catalogue. Without it we fall back to the (English-only) pacman pkgdesc. +option(ENABLE_APPSTREAM "Use AppStream metadata for localized descriptions" ON) +if(ENABLE_APPSTREAM) + find_package(AppStreamQt 1.0 QUIET) + if(AppStreamQt_FOUND) + message(STATUS "AppStreamQt found: descriptions will be localized") + else() + message(STATUS "AppStreamQt not found: descriptions will fall back to pkgdesc") + endif() +endif() + ## ## CONFIGURATION ## @@ -134,6 +146,11 @@ include_directories(${CMAKE_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE project_warnings project_options Qt6::Widgets Threads::Threads spdlog::spdlog fmt::fmt ryml::ryml cpr::cpr backend-rustlib-cxxbridge PkgConfig::LIBALPM) +if(AppStreamQt_FOUND) + target_link_libraries(${PROJECT_NAME} PRIVATE AppStreamQt) + target_compile_definitions(${PROJECT_NAME} PRIVATE HAVE_APPSTREAM) +endif() + option(ENABLE_UNITY "Enable Unity builds of projects" OFF) if(ENABLE_UNITY) # Add for any project you want to apply unity builds for diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cc5039d..e29209a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -51,6 +51,12 @@ #include // for array #include // for ranges::* +#ifdef HAVE_APPSTREAM +#include +#include +#include +#endif + #include #include #include @@ -133,6 +139,7 @@ void MainWindow::setup() noexcept { const QString icon = "software-update-available-symbolic"; const QIcon backup_icon = QIcon(":/icons/software-update-available.png"); m_ui->icon->setIcon(QIcon::fromTheme(icon, backup_icon)); + loadAppStreamSummaries(); fetch_net_pkglist(); refreshPopularApps(); @@ -399,6 +406,41 @@ void processMap(MainWindow& window, const std::string& parent_category, ryml::No } } +// Collect translated application summaries from the distribution's AppStream +// catalogue, keyed by package name. Does nothing when AppStream is unavailable, +// in which case callers fall back to the English pkgdesc. +void MainWindow::loadAppStreamSummaries() noexcept { +#ifdef HAVE_APPSTREAM + spdlog::debug("+++ {} +++", __PRETTY_FUNCTION__); + + AppStream::Pool pool; + // Only the distro catalogue carries ; skipping the other sources + // keeps the pass short without losing any package mapping. + pool.setFlags(AppStream::Pool::FlagLoadOsCatalog); + if (!pool.load()) { + spdlog::warn("AppStream metadata unavailable, falling back to pkgdesc: {}", pool.lastError().toStdString()); + return; + } + + // A single pass over the catalogue is cheaper than one lookup per package. + for (const auto& component : pool.components()) { + const auto& summary = component.summary(); + if (summary.isEmpty()) { + continue; + } + const bool is_app = (component.kind() == AppStream::Component::KindDesktopApp); + for (const auto& pkgname : component.packageNames()) { + // A package may be claimed by several components; a desktop + // application describes it better than an addon or a codec. + if (is_app || !m_appstream_summaries.contains(pkgname)) { + m_appstream_summaries.insert(pkgname, summary); + } + } + } + spdlog::debug("AppStream summaries loaded: {}", m_appstream_summaries.size()); +#endif +} + // Load data from Github repo void MainWindow::fetch_net_pkglist() noexcept { spdlog::debug("+++ {} +++", __PRETTY_FUNCTION__); @@ -456,8 +498,13 @@ void MainWindow::processFile(const std::string& group, const std::string& catego QString install_names; QString uninstall_names; - if (auto pkg = m_alpm_manager->get_package_view(names[0])) { - description = QString(pkg->desc.data()); + // Prefer the AppStream summary, which is translated into the user's + // language; pkgdesc from the pacman database is English only. + description = m_appstream_summaries.value(QString::fromStdString(names[0])); + if (description.isEmpty()) { + if (auto pkg = m_alpm_manager->get_package_view(names[0])) { + description = QString(pkg->desc.data()); + } } install_names = QString::fromStdString(fmt::format("{} {}", names[0], utils::join_range(names.begin() + 1, names.end(), ' '))); @@ -702,7 +749,10 @@ void MainWindow::displayPackages() noexcept { widget_item->setCheckState(TreeCol::Check, Qt::Unchecked); widget_item->setText(TreeCol::Name, key); widget_item->setText(TreeCol::Version, value.at(0)); - widget_item->setText(TreeCol::Description, value.at(1)); + // Same as for the popular apps: a translated AppStream summary if we + // have one, otherwise the English pkgdesc. + const auto& as_summary = m_appstream_summaries.value(key); + widget_item->setText(TreeCol::Description, as_summary.isEmpty() ? value.at(1) : as_summary); widget_item->setText(TreeCol::Displayed, QStringLiteral("true")); // all items are displayed till filtered // update tree diff --git a/src/mainwindow.hpp b/src/mainwindow.hpp index 9935520..abec897 100644 --- a/src/mainwindow.hpp +++ b/src/mainwindow.hpp @@ -49,6 +49,7 @@ #include #include +#include #include #include #include @@ -137,6 +138,7 @@ class MainWindow : public QDialog { void ifDownloadFailed() noexcept; void listFlatpakRemotes() noexcept; void listSizeInstalledFP() noexcept; + void loadAppStreamSummaries() noexcept; void fetch_net_pkglist() noexcept; void processFile(const std::string& group, const std::string& category, const std::vector& names) noexcept; void refreshPopularApps() noexcept; @@ -214,6 +216,10 @@ class MainWindow : public QDialog { alpm::AlpmManagerPtr m_alpm_manager; + /// Maps a package name to its AppStream summary, translated into the + /// user's language. Empty when no AppStream metadata is available. + QHash m_appstream_summaries{}; + QString m_indexFilterFP{}; bool m_warning_flatpaks{}; bool m_setup_assistant_mode{};