[frameworks/kcoreaddons] /: aboutData: Add support for AppStream URLs

Volker Krause <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit 311502af5279922df2d84218289fce4160e66163 by Volker Krause.
Committed on 22/07/2026 at 15:38.
Pushed by vkrause into branch 'master'.

aboutData: Add support for AppStream URLs

This adds API to access URLs in the AppStream metadata. Rather than adding
individual properties for each URL type, this adds a generic API with an
enum key, making this easier to extend.

M  +1    -0    autotests/data/org.kde.coreaddons.test-app.xml
M  +3    -0    autotests/kaboutdatatest.cpp
M  +52   -11   src/lib/kaboutdata.cpp
M  +53   -0    src/lib/kaboutdata.h
M  +6    -0    src/qml/types.h

https://invent.kde.org/frameworks/kcoreaddons/-/commit/311502af5279922df2d84218289fce4160e66163

diff --git a/autotests/data/org.kde.coreaddons.test-app.xml b/autotests/data/org.kde.coreaddons.test-app.xml
index b6029bee..973d97f0 100644
--- a/autotests/data/org.kde.coreaddons.test-app.xml
+++ b/autotests/data/org.kde.coreaddons.test-app.xml
@@ -60,6 +60,7 @@
   </releases>
   <custom>
     <value key="KDE::matrix">#frameworks-devel:kde.org</value>
+    <value key="FOO::something_else">some value</value>
   </custom>
   <recommends>
     <control>keyboard</control>
diff --git a/autotests/kaboutdatatest.cpp b/autotests/kaboutdatatest.cpp
index 359a8852..285f47ec 100644
--- a/autotests/kaboutdatatest.cpp
+++ b/autotests/kaboutdatatest.cpp
@@ -422,6 +422,9 @@ void KAboutDataTest::testAppStream()
     QCOMPARE(aboutData.licenses()[0].name(), "LGPL v2"_L1);
     QCOMPARE(aboutData.licenses()[0].spdx(), "LGPL-2.0+"_L1);
     QCOMPARE(aboutData.homepage(), "https://apps.kde.org/coreaddons-test-app"_L1);
+    QCOMPARE(aboutData.url(KAboutData::Homepage), "https://apps.kde.org/coreaddons-test-app"_L1);
+    QCOMPARE(aboutData.url(KAboutData::VCSBrowser), "https://invent.kde.org/frameworks/kcoreaddons"_L1);
+    QCOMPARE(aboutData.url(KAboutData::XKDEMatrixRoom), "#frameworks-devel:kde.org"_L1);
     QCOMPARE(aboutData.bugAddress(), "https://bugs.kde.org/enter_bug.cgi?format=guided&product=frameworks-coreaddons"_L1);
     QCOMPARE(aboutData.displayName(), "Test App (untranslated)"_L1);
     QCOMPARE(aboutData.shortDescription(), "Test App Description (untranslated)"_L1);
diff --git a/src/lib/kaboutdata.cpp b/src/lib/kaboutdata.cpp
index c3871e2a..52eef2db 100644
--- a/src/lib/kaboutdata.cpp
+++ b/src/lib/kaboutdata.cpp
@@ -603,7 +603,7 @@ public:
     QString _shortDescription;
     QString _copyrightStatement;
     QString _otherText;
-    QString _homepageAddress;
+    QHash<KAboutData::UrlType, QString> _urls;
     QList<KAboutPerson> _authorList;
     QList<KAboutPerson> _creditList;
     QList<KAboutPerson> _translatorList;
@@ -653,7 +653,7 @@ KAboutData::KAboutData(const QString &_componentName,
     d->_licenseList.append(KAboutLicense(licenseType, this));
     d->_copyrightStatement = _copyrightStatement;
     d->_otherText = text;
-    d->_homepageAddress = homePageAddress;
+    d->_urls[KAboutData::Homepage] = homePageAddress;
     d->_bugAddress = bugAddress.toUtf8();
 
     QUrl homePageUrl(homePageAddress);
@@ -909,7 +909,7 @@ KAboutData &KAboutData::setOtherText(const QString &_otherText)
 
 KAboutData &KAboutData::setHomepage(const QString &homepage)
 {
-    d->_homepageAddress = homepage;
+    d->_urls[KAboutData::Homepage] = homepage;
     return *this;
 }
 
@@ -993,7 +993,7 @@ QString KAboutData::shortDescription() const
 
 QString KAboutData::homepage() const
 {
-    return d->_homepageAddress;
+    return d->_urls.value(KAboutData::Homepage);
 }
 
 QString KAboutData::bugAddress() const
@@ -1158,6 +1158,17 @@ QString KAboutData::desktopFileName() const
 #endif
 }
 
+KAboutData &KAboutData::setUrl(KAboutData::UrlType type, const QString &url)
+{
+    d->_urls[type] = url;
+    return *this;
+}
+
+QString KAboutData::url(KAboutData::UrlType type) const
+{
+    return d->_urls.value(type);
+}
+
 KAboutData &KAboutData::addRelease(KAboutRelease &&release)
 {
     d->_releaseList.push_back(std::move(release));
@@ -1502,6 +1513,24 @@ struct AppDataDesc {
     return l;
 }
 
+struct {
+    QLatin1StringView name;
+    KAboutData::UrlType type;
+} static constexpr const url_type_map[] = {
+    {"homepage"_L1, KAboutData::Homepage},
+    {"bugtracker"_L1, KAboutData::Bugtracker},
+    {"faq"_L1, KAboutData::Faq},
+    {"help"_L1, KAboutData::Help},
+    {"donation"_L1, KAboutData::Donation},
+    {"translate"_L1, KAboutData::Translate},
+    {"contact"_L1, KAboutData::Contact},
+    {"vcs-browser"_L1, KAboutData::VCSBrowser},
+    {"contribute"_L1, KAboutData::Contribute},
+    {"KDE::matrix"_L1, KAboutData::XKDEMatrixRoom},
+    {"KDE::forum"_L1, KAboutData::XKDEForum},
+    {"KDE::mastodon"_L1, KAboutData::XKDEMastodon},
+};
+
 KAboutData KAboutData::fromAppStreamFile(const QString &appStreamFileName)
 {
     KAboutData *aboutData = s_registry->m_appData;
@@ -1525,7 +1554,7 @@ KAboutData KAboutData::fromAppStreamFile(const QString &appStreamFileName)
             continue;
         }
 
-        if (reader.name() == "component"_L1) {
+        if (reader.name() == "component"_L1 || reader.name() == "custom"_L1) {
             // recurse into
         } else if (reader.name() == "releases"_L1) {
             aboutData->d->_releaseList.clear();
@@ -1551,12 +1580,15 @@ KAboutData KAboutData::fromAppStreamFile(const QString &appStreamFileName)
             appSummary[lang] = reader.readElementText();
         } else if (reader.name() == "url"_L1) {
             const auto type = reader.attributes().value("type"_L1);
-            if (type == "homepage"_L1) {
-                aboutData->setHomepage(reader.readElementText());
-            } else if (type == "bugtracker"_L1) {
-                aboutData->setBugAddress(reader.readElementText().toUtf8());
-            } else {
-                reader.skipCurrentElement();
+            for (const auto &m : url_type_map) {
+                if (m.name != type) {
+                    continue;
+                }
+                aboutData->setUrl(m.type, reader.readElementText());
+                if (m.name == "bugtracker"_L1) {
+                    aboutData->setBugAddress(aboutData->url(KAboutData::Bugtracker).toUtf8());
+                }
+                break;
             }
         } else if (reader.name() == "release"_L1) {
             const auto version = reader.attributes().value("version"_L1).toString();
@@ -1585,6 +1617,15 @@ KAboutData KAboutData::fromAppStreamFile(const QString &appStreamFileName)
             if (!version.isEmpty() && !desc.desc.isEmpty()) {
                 aboutData->addRelease(KAboutRelease(version, date, desc.desc, desc.rawDesc, url));
             }
+        } else if (reader.name() == "value"_L1) {
+            const auto key = reader.attributes().value("key"_L1);
+            for (const auto &m : url_type_map) {
+                if (m.name != key) {
+                    continue;
+                }
+                aboutData->setUrl(m.type, reader.readElementText());
+                break;
+            }
         } else {
             reader.skipCurrentElement();
         }
diff --git a/src/lib/kaboutdata.h b/src/lib/kaboutdata.h
index d8b818ac..9b1a640d 100644
--- a/src/lib/kaboutdata.h
+++ b/src/lib/kaboutdata.h
@@ -1635,6 +1635,59 @@ public:
      **/
     QString desktopFileName() const;
 
+    /*!
+     * Types of URLs associated with application metadata.
+     *
+     * This includes URL types defined in the AppStream specification
+     * as well as KDE extensions to that.
+     *
+     * \value Homepage The application homepage.
+     * \value Bugtracker The application's bug tracking system, for users to report issues.
+     * \value Faq A FAQ page for this application.
+     * \value Help User reference or documentation.
+     * \value Donation Information on how to donate to this project.
+     * \value Translate Page about how to contribute to translating the application.
+     * \value Contact Page with contact information of the vendor/developer.
+     * \value VCSBrowser Link to the Git forge or similar with the application source code.
+     * \value Contribute Information on how to contribute to this application.
+     *
+     * \value XKDEMatrixRoom Matrix room for this application (KDE::matrix AppStream extension).
+     * \value XKDEForum Web forum/forum section for this application (KDE::forum AppStream extension).
+     * \value XKDEMastodon Mastodon account about this applications (KDE::mastodon AppStream extension).
+     *
+     * \sa https://www.freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-url
+     * \since 6.29
+     */
+    enum UrlType {
+        Homepage,
+        Bugtracker,
+        Faq,
+        Help,
+        Donation,
+        Translate,
+        Contact,
+        VCSBrowser,
+        Contribute,
+        // KDE extensions
+        XKDEMatrixRoom,
+        XKDEForum,
+        XKDEMastodon,
+        // TODO: wiki: currently not read from AppStream by apps.kde.org
+    };
+    Q_ENUM(UrlType)
+
+    /*!
+     * Sets the URL of the given type.
+     * \since 6.29
+     */
+    KAboutData &setUrl(KAboutData::UrlType type, const QString &url);
+    /*!
+     * Returns the URL of the given type, if available.
+     * This is typically populated from AppStream application metadata.
+     * \since 6.29
+     */
+    Q_INVOKABLE [[nodiscard]] QString url(KAboutData::UrlType type) const;
+
     /*!
      * Adds a release note for this application.
      * \sa releases()
diff --git a/src/qml/types.h b/src/qml/types.h
index 5a09eeff..d766b38a 100644
--- a/src/qml/types.h
+++ b/src/qml/types.h
@@ -22,4 +22,10 @@ struct KAboutReleaseForeign {
     QML_FOREIGN(KAboutRelease)
 };
 
+namespace KAboutDataForeign
+{
+Q_NAMESPACE
+QML_NAMED_ELEMENT(AboutUrlType)
+QML_FOREIGN_NAMESPACE(KAboutData)
+}
 #endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.