Using Rust bindings for KDE Frameworks
Extend your application with KDE libraries
KDE provides Rust bindings for some KDE libraries. The current list of Rust bindings can be seen in the cxx-kde-frameworks repository.
We will be modifying the existing tutorial example from A full Rust + Kirigami application. Since the code displayed here is only usable from Rust, this tutorial won't be applicable to Using Rust together with C++ as the code displayed there can simply use C++ KDE libraries directly.
⚠️ Subject to changes
The Qt Group has announced the development of
Qt Bridges, new technology for using Qt with other languages. Depending on what happens when this new technology is published, the KDE Frameworks bindings might undergo large changes.
Changes to existing code
Cargo.toml
To add the crate to the project, just add a new entry in the [dependencies] section of Cargo.toml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| [package]
name = "simplemdviewer"
version = "0.1.0"
authors = [ "Konqi the Konqueror <konqi@kde.org>" ]
edition = "2021"
license = "BSD-2-Clause"
[dependencies]
cxx = "1.0.192"
cxx-qt = "0.8.0"
cxx-qt-lib = { version = "0.8.0", features = ["qt_full"] }
cxx-qt-lib-extras = "0.8"
markdown = "1.0"
cxx-kde-frameworks = { git = "https://invent.kde.org/libraries/cxx-kde-frameworks.git" }
[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { version = "0.8", features = [ "link_qt_object_files" ] }
|
Triggering a rebuild with either cmake --build build/ or cargo build --target-dir build/ should pull the new dependency automatically.
src/main.rs
The KDE Frameworks bindings we will use are:
- KCrash to generate a Dr. Konqi bug report dialog whenever the application crashes
- KCoreAddons for the project credits (and exposing them to QML)
- KI18n for translations
💡 Reading the API
The API that is exposed to Rust follows the standard Rust naming scheme: snake_case. This applies to class names and functions.
A function like KAboutData::setApplicationData() shown in the C++ API translates to KAboutData::set_application_data().
All three of them will be used in the Rust entrypoint code, main.rs.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QQuickStyle, QString, QUrl, QByteArray};
use cxx_qt_lib_extras::QApplication;
use cxx_qt::casting::Upcast;
use cxx_kde_frameworks::kcrash::KCrash;
use cxx_kde_frameworks::kcoreaddons::{KAboutData, License};
use cxx_kde_frameworks::ki18n::{self, i18nc, KLocalizedString};
use std::env;
mod mdconverter;
fn main() {
let mut app = QApplication::new();
KCrash::initialize();
KLocalizedString::set_application_domain(&QByteArray::from("simplemdviewer"));
// To associate the executable to the installed desktop file
QGuiApplication::set_desktop_file_name(&QString::from("org.kde.simplemdviewer"));
// To ensure the style is set correctly
if env::var("QT_QUICK_CONTROLS_STYLE").is_err() {
QQuickStyle::set_style(&QString::from("org.kde.desktop"));
}
let about_data = KAboutData::from(
// componentName
QString::from("simplemdviewer"),
// displayName
i18nc("@title", "Simplemdviewer"),
// version
QString::from("1.0"),
// shortDescription
QString::from("Rust application to test KDE Frameworks bindings"),
// license
License::GPL_V3
);
KAboutData::set_application_data(&about_data);
let mut engine = QQmlApplicationEngine::new();
if let Some(mut engine) = engine.as_mut() {
ki18n::setup_localized_context(engine.as_mut().upcast_pin());
engine.load(&QUrl::from(
"qrc:/qt/qml/org/kde/simplemdviewer/src/qml/Main.qml"
));
}
if let Some(app) = app.as_mut() {
app.exec();
}
}
|
Add the necessary use declarations to import the classes from the cxx_kde_frameworks crate, as well as QByteArray from cxx_qt_lib which is needed for one of the functions:
1
2
3
4
5
6
7
| use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QQuickStyle, QString, QUrl, QByteArray};
use cxx_qt_lib_extras::QApplication;
use cxx_qt::casting::Upcast;
use cxx_kde_frameworks::kcrash::KCrash;
use cxx_kde_frameworks::kcoreaddons::{KAboutData, License};
use cxx_kde_frameworks::ki18n::{self, i18nc, KLocalizedString};
use std::env;
|
All it takes to initialize KCrash is a single line after the QApplication is created:
12
13
14
| let mut app = QApplication::new();
KCrash::initialize();
KLocalizedString::set_application_domain(&QByteArray::from("simplemdviewer"));
|
While KI18n requires two lines: one for setting the name of the application domain after the QApplication is created (required to identify which application the translation belongs to):
11
12
13
14
| fn main() {
let mut app = QApplication::new();
KCrash::initialize();
KLocalizedString::set_application_domain(&QByteArray::from("simplemdviewer"));
|
And the other to apply internationalization to the QML engine:
37
38
39
40
41
42
43
|
let mut engine = QQmlApplicationEngine::new();
if let Some(mut engine) = engine.as_mut() {
ki18n::setup_localized_context(engine.as_mut().upcast_pin());
engine.load(&QUrl::from(
"qrc:/qt/qml/org/kde/simplemdviewer/src/qml/Main.qml"
));
|
Note that the engine is now mutable (mut).
Lastly, we use the KAboutData constructor (translated to Rust using the From trait) to define the metadata information of the application and set it:
24
25
26
27
28
29
30
31
32
33
34
35
36
| let about_data = KAboutData::from(
// componentName
QString::from("simplemdviewer"),
// displayName
i18nc("@title", "Simplemdviewer"),
// version
QString::from("1.0"),
// shortDescription
QString::from("Rust application to test KDE Frameworks bindings"),
// license
License::GPL_V3
);
KAboutData::set_application_data(&about_data);
|
Note that the license enum is called License instead of KAboutLicense, as internally it is just a helper enum rather than the whole KAboutLicense class. The enum value names still match the C++ API.
The KI18n bindings expose common i18n() functions used for translatable text, such as i18nc() which is used for adding context to strings following KDE's Programmer's Guide to Internationalization.
CMakeLists.txt
To test and visualize the result of using KAboutData in our code, we will add an additional dependency to the project: Kirigami Addons.
Make sure to add it to the root CMakeLists.txt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| cmake_minimum_required(VERSION 3.28)
project(simplemdviewer)
find_package(ECM 6.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(ECMUninstallTarget)
include(ECMFindQmlModule)
ecm_find_qmlmodule(org.kde.kirigami REQUIRED)
ecm_find_qmlmodule(org.kde.kirigamiaddons.formcard REQUIRED)
find_package(KF6 REQUIRED COMPONENTS QQC2DesktopStyle)
set(CARGO_INNER_TARGET_DIR debug)
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(CARGO_OPTIONS --release)
set(CARGO_INNER_TARGET_DIR release)
endif()
add_custom_target(simplemdviewer
ALL
COMMAND cargo build --target-dir ${CMAKE_CURRENT_BINARY_DIR} ${CARGO_OPTIONS}
BYPRODUCTS ${CMAKE_BINARY_DIR}/${CARGO_INNER_TARGET_DIR}
)
install(
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_INNER_TARGET_DIR}/simplemdviewer
DESTINATION ${KDE_INSTALL_BINDIR}
)
install(FILES org.kde.simplemdviewer.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.kde.simplemdviewer.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES org.kde.simplemdviewer.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
This makes the presence of the FormCard QML module required at compile time.
src/qml/Main.qml
Kirigami Addons provides a FormCard component that can be used to generate a standard About Application page, as mentioned in FormCard About Pages. It can be added to the default actions property of a new Kirigami.GlobalDrawer inside the top level Kirigami.ApplicationWindow.
We can use Qt.createComponent() to generate the page on the fly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
| import QtQuick
import QtQuick.Controls as Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.simplemdviewer
Kirigami.ApplicationWindow {
id: root
title: "Simple Markdown Viewer in Rust 🦀"
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 20
width: minimumWidth
height: minimumHeight
pageStack.initialPage: initPage
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
icon.name: "kde"
text: "Open About page"
onTriggered: pageStack.push(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
}
]
}
Component {
id: initPage
Kirigami.Page {
title: "Markdown Viewer"
MdConverter {
id: mdconverter
sourceText: sourceArea.text
}
ColumnLayout {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Controls.TextArea {
id: sourceArea
placeholderText: "Write some Markdown code here"
wrapMode: Text.WrapAnywhere
Layout.fillWidth: true
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
}
RowLayout {
Layout.fillWidth: true
Controls.Button {
text: "Format"
onClicked: formattedText.text = mdconverter.mdFormat()
}
Controls.Button {
text: "Clear"
onClicked: {
sourceArea.text = "";
formattedText.text = "";
}
}
}
Controls.Label {
id: formattedText
textFormat: Text.RichText
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
}
}
}
}
}
|
As simple as that.
Final test run
At last, build, install and run your new application:
cmake -B build/ --install-prefix ~/.local
cmake --build build/
cmake --install build/
simplemdviewer
You can also try building it with Cargo:
cargo build --target-dir build/
cargo run --target-dir build/
You should now see a button that when clicked leads you to a new About Page that uses the same application metadata from main.rs set with KAboutData.
Our app so far
CMakeLists.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| cmake_minimum_required(VERSION 3.28)
project(simplemdviewer)
find_package(ECM 6.0 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(KDEInstallDirs)
include(ECMUninstallTarget)
include(ECMFindQmlModule)
ecm_find_qmlmodule(org.kde.kirigami REQUIRED)
ecm_find_qmlmodule(org.kde.kirigamiaddons.formcard REQUIRED)
find_package(KF6 REQUIRED COMPONENTS QQC2DesktopStyle)
set(CARGO_INNER_TARGET_DIR debug)
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
set(CARGO_OPTIONS --release)
set(CARGO_INNER_TARGET_DIR release)
endif()
add_custom_target(simplemdviewer
ALL
COMMAND cargo build --target-dir ${CMAKE_CURRENT_BINARY_DIR} ${CARGO_OPTIONS}
BYPRODUCTS ${CMAKE_BINARY_DIR}/${CARGO_INNER_TARGET_DIR}
)
install(
PROGRAMS ${CMAKE_CURRENT_BINARY_DIR}/${CARGO_INNER_TARGET_DIR}/simplemdviewer
DESTINATION ${KDE_INSTALL_BINDIR}
)
install(FILES org.kde.simplemdviewer.desktop DESTINATION ${KDE_INSTALL_APPDIR})
install(FILES org.kde.simplemdviewer.metainfo.xml DESTINATION ${KDE_INSTALL_METAINFODIR})
install(FILES org.kde.simplemdviewer.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/hicolor/scalable/apps)
|
Cargo.toml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| [package]
name = "simplemdviewer"
version = "0.1.0"
authors = [ "Konqi the Konqueror <konqi@kde.org>" ]
edition = "2021"
license = "BSD-2-Clause"
[dependencies]
cxx = "1.0.192"
cxx-qt = "0.8.0"
cxx-qt-lib = { version = "0.8.0", features = ["qt_full"] }
cxx-qt-lib-extras = "0.8"
markdown = "1.0"
cxx-kde-frameworks = { git = "https://invent.kde.org/libraries/cxx-kde-frameworks.git" }
[build-dependencies]
# The link_qt_object_files feature is required for statically linking Qt 6.
cxx-qt-build = { version = "0.8", features = [ "link_qt_object_files" ] }
|
build.rs
1
2
3
4
5
6
7
8
9
10
| use cxx_qt_build::{CxxQtBuilder, QmlModule};
fn main() {
CxxQtBuilder::new_qml_module(
QmlModule::new("org.kde.simplemdviewer")
.qml_file("src/qml/Main.qml")
)
.files(["src/mdconverter.rs"])
.build();
}
|
src/main.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
| use cxx_qt_lib::{QGuiApplication, QQmlApplicationEngine, QQuickStyle, QString, QUrl, QByteArray};
use cxx_qt_lib_extras::QApplication;
use cxx_qt::casting::Upcast;
use cxx_kde_frameworks::kcrash::KCrash;
use cxx_kde_frameworks::kcoreaddons::{KAboutData, License};
use cxx_kde_frameworks::ki18n::{self, i18nc, KLocalizedString};
use std::env;
mod mdconverter;
fn main() {
let mut app = QApplication::new();
KCrash::initialize();
KLocalizedString::set_application_domain(&QByteArray::from("simplemdviewer"));
// To associate the executable to the installed desktop file
QGuiApplication::set_desktop_file_name(&QString::from("org.kde.simplemdviewer"));
// To ensure the style is set correctly
if env::var("QT_QUICK_CONTROLS_STYLE").is_err() {
QQuickStyle::set_style(&QString::from("org.kde.desktop"));
}
let about_data = KAboutData::from(
// componentName
QString::from("simplemdviewer"),
// displayName
i18nc("@title", "Simplemdviewer"),
// version
QString::from("1.0"),
// shortDescription
QString::from("Rust application to test KDE Frameworks bindings"),
// license
License::GPL_V3
);
KAboutData::set_application_data(&about_data);
let mut engine = QQmlApplicationEngine::new();
if let Some(mut engine) = engine.as_mut() {
ki18n::setup_localized_context(engine.as_mut().upcast_pin());
engine.load(&QUrl::from(
"qrc:/qt/qml/org/kde/simplemdviewer/src/qml/Main.qml"
));
}
if let Some(app) = app.as_mut() {
app.exec();
}
}
|
src/mdconverter.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| use std::pin::Pin;
use cxx_qt_lib::QString;
#[cxx_qt::bridge]
mod ffi {
unsafe extern "C++" {
include!("cxx-qt-lib/qstring.h");
type QString = cxx_qt_lib::QString;
}
#[auto_cxx_name]
unsafe extern "RustQt" {
#[qobject]
#[qml_element]
#[qproperty(QString, source_text)]
type MdConverter = super::MdConverterStruct;
#[qinvokable]
fn md_format(self: Pin<&mut MdConverter>) -> QString;
}
}
#[derive(Default)]
pub struct MdConverterStruct {
source_text: QString,
}
impl ffi::MdConverter {
pub fn md_format(self: Pin<&mut Self>) -> QString {
QString::from(&markdown::to_html(&self.source_text().to_string()))
}
}
|
src/qml/Main.qml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
| import QtQuick
import QtQuick.Controls as Controls
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.simplemdviewer
Kirigami.ApplicationWindow {
id: root
title: "Simple Markdown Viewer in Rust 🦀"
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 20
width: minimumWidth
height: minimumHeight
pageStack.initialPage: initPage
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
icon.name: "kde"
text: "Open About page"
onTriggered: pageStack.push(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
}
]
}
Component {
id: initPage
Kirigami.Page {
title: "Markdown Viewer"
MdConverter {
id: mdconverter
sourceText: sourceArea.text
}
ColumnLayout {
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Controls.TextArea {
id: sourceArea
placeholderText: "Write some Markdown code here"
wrapMode: Text.WrapAnywhere
Layout.fillWidth: true
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
}
RowLayout {
Layout.fillWidth: true
Controls.Button {
text: "Format"
onClicked: formattedText.text = mdconverter.mdFormat()
}
Controls.Button {
text: "Clear"
onClicked: {
sourceArea.text = "";
formattedText.text = "";
}
}
}
Controls.Label {
id: formattedText
textFormat: Text.RichText
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
}
}
}
}
}
|
org.kde.simplemdviewer.desktop
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| [Desktop Entry]
Name=Simple Markdown Viewer in Rust and Kirigami
Name[ca]=Visualitzador senzill de Markdown en Rust i Kirigami
Name[es]=Sencillo visor de Markdown en Rust y Kirigami
Name[fr]=Afficheur simple pour langage « Markdown » dans les langages Rust et Kirigami
Name[it]=Visore Markdown semplice in Rust e Kirigami
Name[nl]=Eenvoudige Markdown-viewer in Rust en Kirigami
Name[pt_BR]=Visualizador de Markdown simples em Rust e Kirigami
Name[ro]=Vizualizor simplu de Markdown în Rust și Kirigami
Name[sl]=Preprosti ogledovalnik Markdown v Rustu in Kirigami
Name[sv]=Enkel Markdown-visning med Rust och Kirigami
Name[tr]=Rust ve Kirigami ile Basit Markdown Görüntüleyicisi
Name[uk]=Простий переглядач Markdown з використанням Rust і Kirigami
Exec=simplemdviewer
Icon=org.kde.simplemdviewer
Type=Application
Terminal=false
Categories=Utility
|
org.kde.simplemdviewer.metainfo.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| <?xml version="1.0" encoding="utf-8"?>
<component type="desktop-application">
<id>org.kde.simplemdviewer</id>
<launchable type="desktop-id">org.kde.simplemdviewer.desktop</launchable>
<name>Simple Markdown Viewer in Rust and Kirigami</name>
<name xml:lang="ca">Visualitzador senzill de Markdown en Rust i Kirigami</name>
<name xml:lang="es">Sencillo visor de Markdown en Rust y Kirigami</name>
<name xml:lang="fr">Afficheur simple pour langage « Markdown » en langages Rust et Kirigami</name>
<name xml:lang="it">Visore Markdown semplice in Rust e Kirigami</name>
<name xml:lang="nl">Eenvoudige Markdown-viewer in Rust en Kirigami</name>
<name xml:lang="pt-BR">Visualizador de Markdown simples em Rust e Kirigami</name>
<name xml:lang="sl">Preprost pregledovalnik Markdown v Rustu in Kirigamiju</name>
<name xml:lang="sv">Enkel Markdown-visning med Rust och Kirigami</name>
<name xml:lang="tr">Rust ve Kirigami ile Basit Markdown Görüntüleyicisi</name>
<name xml:lang="uk">Простий переглядач Markdown з використанням Rust і Kirigami</name>
<metadata_license/>
<project_license>MPL-2.0</project_license>
<summary>Markdown viewer</summary>
<summary xml:lang="ca">Visualitzador de Markdown</summary>
<summary xml:lang="es">Visor de Markdown</summary>
<summary xml:lang="fr">Afficheur de fichiers « Markdown »</summary>
<summary xml:lang="it">Visore Markdown</summary>
<summary xml:lang="nl">Markdown-viewer</summary>
<summary xml:lang="pt-BR">Visualizador de Markdown</summary>
<summary xml:lang="sl">Pregledovalnik markdown</summary>
<summary xml:lang="sv">Markdown-visning</summary>
<summary xml:lang="tr">Markdown görüntüleyicisi</summary>
<summary xml:lang="uk">Засіб перегляду Markdown</summary>
<description>
<p>A simple Markdown viewer application</p>
<p xml:lang="ca">Una aplicació senzilla de visualització de Markdown</p>
<p xml:lang="es">Una sencilla aplicación de visor de Markdown</p>
<p xml:lang="fr">Une application simple d'affichage pour le langage « Markdown »</p>
<p xml:lang="it">L'applicazione di un visore Markdown semplice</p>
<p xml:lang="nl">Een eenvoudige toepassing als Markdown-viewer</p>
<p xml:lang="pt-BR">Um aplicativo visualizador de Markdown simples</p>
<p xml:lang="sl">Preprosta aplikacija pregledovalnika Markdown</p>
<p xml:lang="sv">Ett enkelt Markdown-visningsprogram</p>
<p xml:lang="tr">Basit bir Markdown görüntüleyici uygulaması</p>
<p xml:lang="uk">Проста програма для перегляду Markdown</p>
</description>
<developer_name>Konqi the Konqueror</developer_name>
<developer_name xml:lang="ca">Konqi el Konqueror</developer_name>
<developer_name xml:lang="es">Konqi el Konquistador</developer_name>
<developer_name xml:lang="fr">Konqi le Konqueror</developer_name>
<developer_name xml:lang="it">Konqi il conquistatore</developer_name>
<developer_name xml:lang="nl">Konqi de Konqueror</developer_name>
<developer_name xml:lang="pt-BR">Konqi o Konqueror</developer_name>
<developer_name xml:lang="sl">Konqi zmagovalec (Konqueror)</developer_name>
<developer_name xml:lang="sv">Konqi Konqueror</developer_name>
<developer_name xml:lang="tr">Konqueror Konqi</developer_name>
<developer_name xml:lang="uk">Konqi — Konqueror</developer_name>
<update_contact>konqi@kde.org</update_contact>
<releases>
<release version="0.1.0" date="2025-07-01"/>
</releases>
</component>
|