commit 40b4924dad49dcf4d631a6e0dfe5ad03224eb121
Author: Fabio Forni <development@redaril.me>
Date:   Thu Apr 11 21:38:27 2024 +0200

    Allow to install hardware drivers

diff --git a/libdiscover/Category/Category.cpp b/libdiscover/Category/Category.cpp
index 7e29659..3cd58ad 100644
--- a/libdiscover/Category/Category.cpp
+++ b/libdiscover/Category/Category.cpp
@@ -33,7 +33,8 @@ Category::Category(const QString &name,
                    const CategoryFilter &filter,
                    const QSet<QString> &pluginName,
                    const QVector<Category *> &subCategories,
-                   bool isAddons)
+                   bool isAddons,
+                   bool isDrivers)
     : QObject(nullptr)
     , m_name(name)
     , m_iconString(iconName)
@@ -41,7 +42,8 @@ Category::Category(const QString &name,
     , m_subCategories(subCategories)
     , m_plugins(pluginName)
     , m_isAddons(isAddons)
-    , m_priority(isAddons ? 5 : 0)
+    , m_isDrivers(isDrivers)
+    , m_priority(isAddons || isDrivers ? 5 : 0)
 {
     setObjectName(m_name);
 
@@ -82,10 +84,13 @@ void Category::parseData(const QString &path, QXmlStreamReader *xml)
             m_isAddons = true;
             m_priority = 5;
             xml->readNext();
+        } else if (xml->name() == QLatin1String("Drivers")) {
+            m_isDrivers = true;
+            m_priority = 5;
+            xml->readNext();
         } else if (xml->name() == QLatin1String("Icon")) {
             m_iconString = xml->readElementText();
-        } else if (xml->name() == QLatin1String("Include")
-                   || xml->name() == QLatin1String("Categories")) {
+        } else if (xml->name() == QLatin1String("Include") || xml->name() == QLatin1String("Categories")) {
             const QString opening = xml->name().toString();
             while (!xml->atEnd() && !xml->hasError()) {
                 xml->readNext();
diff --git a/libdiscover/Category/Category.h b/libdiscover/Category/Category.h
index 7d4c99e..4593254 100644
--- a/libdiscover/Category/Category.h
+++ b/libdiscover/Category/Category.h
@@ -60,7 +60,8 @@ public:
              const CategoryFilter &filters,
              const QSet<QString> &pluginName,
              const QVector<Category *> &subCategories,
-             bool isAddons);
+             bool isAddons,
+             bool isDrivers);
     ~Category() override;
 
     QString name() const;
@@ -87,6 +88,10 @@ public:
     {
         return m_isAddons;
     }
+    bool isDrivers() const
+    {
+        return m_isDrivers;
+    }
     qint8 priority() const
     {
         return m_priority;
@@ -119,6 +124,7 @@ private:
     CategoryFilter parseIncludes(QXmlStreamReader *xml);
     QSet<QString> m_plugins;
     bool m_isAddons = false;
+    bool m_isDrivers = false;
     qint8 m_priority = 0;
     QTimer *m_subCategoriesChanged;
 };
diff --git a/libdiscover/appstream/AppStreamUtils.cpp b/libdiscover/appstream/AppStreamUtils.cpp
index 8219369..052b288 100644
--- a/libdiscover/appstream/AppStreamUtils.cpp
+++ b/libdiscover/appstream/AppStreamUtils.cpp
@@ -7,6 +7,7 @@
 #include "AppStreamUtils.h"
 
 #include "utils.h"
+#include <AppStreamQt/component.h>
 #include <AppStreamQt/pool.h>
 #include <AppStreamQt/release.h>
 #include <AppStreamQt/screenshot.h>
@@ -260,8 +261,12 @@ static void kRemoveDuplicates(AppStream::ComponentBox &input, AppStream::Bundle:
 AppStream::ComponentBox AppStreamUtils::componentsByCategories(AppStream::Pool *pool, Category *cat, AppStream::Bundle::Kind kind)
 {
     AppStream::ComponentBox ret(AppStream::ComponentBox::FlagNoChecks);
-    for (const auto &categoryName : cat->involvedCategories()) {
-        ret += pool->componentsByCategories({categoryName});
+    if (cat->isDrivers()) {
+        ret += pool->componentsByKind(AppStream::Component::KindDriver);
+    } else {
+        for (const auto &categoryName : cat->involvedCategories()) {
+            ret += pool->componentsByCategories({categoryName});
+        }
     }
     kRemoveDuplicates(ret, kind);
     return ret;
diff --git a/libdiscover/backends/KNSBackend/KNSBackend.cpp b/libdiscover/backends/KNSBackend/KNSBackend.cpp
index 8beb0c0..110e4f2 100644
--- a/libdiscover/backends/KNSBackend/KNSBackend.cpp
+++ b/libdiscover/backends/KNSBackend/KNSBackend.cpp
@@ -188,9 +188,9 @@ KNSBackend::KNSBackend(QObject *parent, const QString &iconName, const QString &
         m_categories += cats;
         for (const auto &cat : cats) {
             if (m_hasApplications)
-                categories << new Category(cat, QStringLiteral("applications-other"), {CategoryFilter::CategoryNameFilter, cat}, backendName, {}, true);
+                categories << new Category(cat, QStringLiteral("applications-other"), {CategoryFilter::CategoryNameFilter, cat}, backendName, {}, true, false);
             else
-                categories << new Category(cat, QStringLiteral("plasma"), {CategoryFilter::CategoryNameFilter, cat}, backendName, {}, true);
+                categories << new Category(cat, QStringLiteral("plasma"), {CategoryFilter::CategoryNameFilter, cat}, backendName, {}, true, false);
         }
     }
 
@@ -227,12 +227,13 @@ KNSBackend::KNSBackend(QObject *parent, const QString &iconName, const QString &
     m_engine->init(m_name);
 
     if (m_hasApplications) {
-        auto actualCategory = new Category(m_displayName, QStringLiteral("applications-other"), filter, backendName, topCategories, false);
+        auto actualCategory = new Category(m_displayName, QStringLiteral("applications-other"), filter, backendName, topCategories, false, false);
         auto applicationCategory = new Category(i18n("Applications"), //
                                                 QStringLiteral("applications-internet"),
                                                 filter,
                                                 backendName,
                                                 {actualCategory},
+                                                false,
                                                 false);
         const QVector<CategoryFilter> filters = {{CategoryFilter::CategoryNameFilter, QLatin1String("Application")}, filter};
         applicationCategory->setFilter({CategoryFilter::AndFilter, filters});
@@ -271,11 +272,11 @@ KNSBackend::KNSBackend(QObject *parent, const QString &iconName, const QString &
             QStringLiteral("ksplash.knsrc"),       QStringLiteral("window-decorations.knsrc"),
         };
         const auto iconName = knsrcPlasma.contains(fileName) ? QStringLiteral("plasma") : QStringLiteral("applications-other");
-        auto actualCategory = new Category(m_displayName, iconName, filter, backendName, categories, true);
+        auto actualCategory = new Category(m_displayName, iconName, filter, backendName, categories, true, false);
         actualCategory->setParent(this);
 
         const auto topLevelName = knsrcPlasma.contains(fileName) ? i18n("Plasma Addons") : i18n("Application Addons");
-        auto addonsCategory = new Category(topLevelName, iconName, filter, backendName, {actualCategory}, true);
+        auto addonsCategory = new Category(topLevelName, iconName, filter, backendName, {actualCategory}, true, false);
         m_rootCategories = {addonsCategory};
     }
 
diff --git a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp
index a6f31b6..4241933 100644
--- a/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp
+++ b/libdiscover/backends/PackageKitBackend/AppPackageKitResource.cpp
@@ -11,6 +11,7 @@
 #include <AppStreamQt/image.h>
 #include <AppStreamQt/release.h>
 #include <AppStreamQt/screenshot.h>
+#include <AppStreamQt/systeminfo.h>
 #include <AppStreamQt/version.h>
 #include <KLocalizedString>
 #include <KService>
@@ -22,6 +23,7 @@
 #include <QStandardPaths>
 #include <QUrlQuery>
 #include <appstream/AppStreamUtils.h>
+#include <qstringliteral.h>
 
 using namespace Qt::StringLiterals;
 
@@ -105,12 +107,37 @@ QStringList AppPackageKitResource::mimetypes() const
 
 static constexpr auto s_addonKinds = {AppStream::Component::KindAddon, AppStream::Component::KindCodec};
 
+static bool hasDeviceModaliases(const AppStream::Component &comp)
+{
+    const auto mods = comp.provided(AppStream::Provided::KindModalias);
+    auto sys = AppStream::SystemInfo();
+    for (const auto &mod : mods.items()) {
+        if (sys.hasDeviceMatchingModalias(mod)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 QStringList AppPackageKitResource::categories()
 {
-    auto cats = m_appdata.categories();
-    if (!kContainsValue(s_addonKinds, m_appdata.kind()))
-        cats.append(QStringLiteral("Application"));
-    return cats;
+    switch (m_appdata.kind()) {
+    case AppStream::Component::KindDriver: {
+        auto cats = QStringList();
+        cats.reserve(2);
+        cats.append(QStringLiteral("Drivers"));
+        if (hasDeviceModaliases(m_appdata)) {
+            cats.append(QStringLiteral("CompatibleDrivers"));
+        }
+        return cats;
+    }
+    default: {
+        auto cats = m_appdata.categories();
+        if (!kContainsValue(s_addonKinds, m_appdata.kind()))
+            cats.append(QStringLiteral("Application"));
+        return cats;
+    }
+    }
 }
 
 QString AppPackageKitResource::comment()
diff --git a/libdiscover/backends/PackageKitBackend/packagekit-backend-categories.xml b/libdiscover/backends/PackageKitBackend/packagekit-backend-categories.xml
index ff1a922..385d83c 100644
--- a/libdiscover/backends/PackageKitBackend/packagekit-backend-categories.xml
+++ b/libdiscover/backends/PackageKitBackend/packagekit-backend-categories.xml
@@ -548,6 +548,28 @@
       </Or>
     </Include>
   </Menu>
+
+  <Menu>
+    <Name>Hardware Drivers</Name>
+    <Icon>cpu</Icon>
+    <Drivers />
+    <Include>
+      <And>
+        <Category>Drivers</Category>
+      </And>
+    </Include>
+    <Menu>
+      <Name>Drivers For This Device</Name>
+      <Icon>cpu</Icon>
+      <Drivers />
+      <Include>
+        <And>
+          <Category>CompatibleDrivers</Category>
+        </And>
+      </Include>
+    </Menu>
+  </Menu>
+
   <Menu>
     <Name>Plasma Addons</Name>
     <Icon>plasma</Icon>
