From noreply at launchpad.net  Thu Apr  9 05:28:44 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:28:44 -0000
Subject: [recipe build #4028252] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177570892398.714995.6517638829793313387.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 11 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028252/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-094

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028252
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:28:44 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:28:44 -0000
Subject: [recipe build #4028250] of ~rkward-devel rkward-kf5-daily-r35 in
 jammy: Failed to build
Message-ID: <177570892459.714995.14121452268138232376.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r35
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r35
 * Distroseries: jammy
 * Duration: 12 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028250/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-037

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028250
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:28:45 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:28:45 -0000
Subject: [recipe build #4028249] of ~rkward-devel rkward-kf5-daily-r35 in
 focal: Failed to build
Message-ID: <177570892515.714995.12910073283829270535.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r35
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r35
 * Distroseries: focal
 * Duration: 12 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028249/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-038

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028249
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:30:46 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:30:46 -0000
Subject: [recipe build #4028248] of ~rkward-devel rkward-kf5-daily-r35 in
 bionic: Failed to build
Message-ID: <177570904622.714995.5331580842147082972.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r35
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r35
 * Distroseries: bionic
 * Duration: 14 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028248/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-046

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r35/+recipebuild/4028248
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:30:47 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:30:47 -0000
Subject: [recipe build #4028259] of ~rkward-devel rkward-kf5-daily in bionic:
 Failed to build
Message-ID: <177570904699.714995.4509140233278553040.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily
 * Distroseries: bionic
 * Duration: 10 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily/+recipebuild/4028259/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-086

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily/+recipebuild/4028259
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:34:25 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:34:25 -0000
Subject: [recipe build #4028255] of ~rkward-devel rkward-kf5-daily-r40 in
 noble: Failed to build
Message-ID: <177570926578.714995.7749551562491149029.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: noble
 * Duration: 16 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028255/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-003

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028255
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:34:26 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:34:26 -0000
Subject: [recipe build #4028253] of ~rkward-devel rkward-kf5-daily-r40 in
 focal: Failed to build
Message-ID: <177570926629.714995.432443989753736081.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: focal
 * Duration: 16 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028253/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-036

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028253
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:34:28 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:34:28 -0000
Subject: [recipe build #4028272] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177570926896.714995.7494238805132784858.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 12 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4028272/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-016

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4028272
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Thu Apr  9 05:34:35 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Thu, 09 Apr 2026 04:34:35 -0000
Subject: [recipe build #4028260] of ~rkward-devel rkward-kf5-daily in focal:
 Failed to build
Message-ID: <177570927518.714995.5171956557023608657.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily
 * Distroseries: focal
 * Duration: 13 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily/+recipebuild/4028260/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-107

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily/+recipebuild/4028260
Your team RKWard Team is the requester of the build.


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Proof_of_concept_QWebview-based_HTML_window?=
Message-ID: <20260409194424.61F131400F15@lerwini.kde.org>

Git commit 618ac10ef7c9fa490cbdd8a3b2731b38ab6eecef by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Proof of concept QWebview-based HTML window

M  +1    -1    CMakeLists.txt
M  +2    -0    rkward/main.cpp
M  +3    -0    rkward/resources.qrc
M  +2    -1    rkward/windows/CMakeLists.txt
M  +3    -1    rkward/windows/rkhtmlviewer.cpp
A  +103  -0    rkward/windows/rkqwebview.cpp     [License: GPL(v2.0+)]
A  +43   -0    rkward/windows/rkqwebview.h     [License: GPL(v2.0+)]
A  +22   -0    rkward/windows/rkqwebview.qml     [License: GPL(v2.0+)]

https://invent.kde.org/education/rkward/-/commit/618ac10ef7c9fa490cbdd8a3b2731b38ab6eecef

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4e6b12242..327cac8f0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,7 +27,7 @@ INCLUDE(ECMAddAppIcon)
 INCLUDE(ECMMarkNonGuiExecutable)
 INCLUDE(FeatureSummary)
 
-FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS Widgets Core Xml Network Qml PrintSupport WebEngineWidgets)
+FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS Widgets Core Xml Network Qml PrintSupport WebEngineWidgets WebView)
 FIND_PACKAGE(KF6 6.0.0 REQUIRED COMPONENTS CoreAddons DocTools I18n XmlGui TextEditor WidgetsAddons Parts Config Notifications WindowSystem Archive BreezeIcons OPTIONAL_COMPONENTS Crash)
 FIND_PACKAGE(Gettext REQUIRED)
 
diff --git a/rkward/main.cpp b/rkward/main.cpp
index d8b9c7f26..c69dbb753 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -59,6 +59,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QThread>
 #include <QTime>
 #include <QUrl>
+#include <QtWebView>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -153,6 +154,7 @@ int main(int argc, char *argv[]) {
 	scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
 	QWebEngineUrlScheme::registerScheme(scheme);
 	BreezeIcons::initIcons(); // install as fallback theme. Too many issues with missing icons, otherwise
+	QtWebView::initialize();
 	QApplication app(argc, argv);
 	KDSingleApplication app_singleton;
 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
diff --git a/rkward/resources.qrc b/rkward/resources.qrc
index 43e1be17a..19b89ffd0 100644
--- a/rkward/resources.qrc
+++ b/rkward/resources.qrc
@@ -24,4 +24,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 <qresource prefix="/org.kde.syntax-highlighting/syntax-addons">
 	<file alias="rkward.xml">syntax/rkward.xml</file>
 </qresource>
+<qresource prefix="/qml">
+	<file alias="rkqwebview.qml">windows/rkqwebview.qml</file>
+</qresource>
 </RCC>
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index d3bb67b98..2192b66a3 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -13,6 +13,7 @@ SET(windows_STAT_SRCS
 	rkhtmlwindow.cpp
 	rkhtmlviewer.cpp
 	rkqwebenginewidget.cpp
+	rkqwebview.cpp
 	rkpdfwindow.cpp
 	rcontrolwindow.cpp
 	detachedwindowcontainer.cpp
@@ -35,4 +36,4 @@ SET(windows_STAT_SRCS
 )
 
 ADD_LIBRARY(windows STATIC ${windows_STAT_SRCS})
-TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport Qt6::WebEngineWidgets KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
+TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport Qt6::WebEngineWidgets Qt6::WebView Qt6::Qml Qt6::Quick Qt6::QuickWidgets KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
index 058ab113c..47c356847 100644
--- a/rkward/windows/rkhtmlviewer.cpp
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -8,6 +8,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "rkhtmlviewer.h"
 
 #include "rkqwebenginewidget.h"
+#include "rkqwebview.h"
 
 #include "../debug.h"
 
@@ -17,7 +18,8 @@ RKHTMLViewer::RKHTMLViewer(QObject *parent) : QObject(parent) {
 
 RKHTMLViewer *RKHTMLViewer::getNew(RKHTMLWindow *parent) {
 	RK_TRACE(APP);
-	return new RKQWebEngineWidget(parent);
+	//return new RKQWebEngineWidget(parent);
+	return new RKQWebView(parent);
 }
 
 #include "rkhtmlviewer.moc"
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
new file mode 100644
index 000000000..df9462428
--- /dev/null
+++ b/rkward/windows/rkqwebview.cpp
@@ -0,0 +1,103 @@
+/*
+rkqwebview - This file is part of the RKWard project. Created: Sun Feb 08 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkqwebview.h"
+
+#include <QMenu>
+#include <QQuickItem>
+#include <QQuickWidget>
+
+#include "rkhtmlwindow.h"
+
+#include "../debug.h"
+
+RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent) {
+	RK_TRACE(APP);
+}
+
+QWidget *RKQWebView::createWidget(QWidget *parent) {
+	RK_TRACE(APP);
+	view = new QQuickWidget(parent);
+	view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
+	view->setResizeMode(QQuickWidget::SizeRootObjectToView);
+	return view;
+}
+
+QUrl RKQWebView::url() const {
+	RK_TRACE(APP);
+	return QUrl();
+}
+
+void RKQWebView::load(const QUrl &url) {
+	RK_TRACE(APP);
+	auto wv = view->rootObject()->findChild<QObject *>("webView");
+	RK_ASSERT(wv);
+	if (!wv) return;
+	wv->setProperty("url", url);
+}
+
+void RKQWebView::print() {
+	RK_TRACE(APP);
+}
+
+void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
+	RK_TRACE(APP);
+}
+
+void RKQWebView::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
+	RK_TRACE(APP);
+}
+
+void RKQWebView::setHTML(const QString &html, const QUrl &url) {
+	RK_TRACE(APP);
+}
+
+bool RKQWebView::installHelpProtocolHandler() {
+	RK_TRACE(APP);
+	return false;
+}
+
+void RKQWebView::findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) {
+	RK_TRACE(APP);
+}
+
+QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
+	RK_TRACE(APP);
+	return new QMenu();
+}
+
+QString RKQWebView::selectedText() const {
+	RK_TRACE(APP);
+
+	return QString();
+}
+
+void RKQWebView::exportPage() {
+	RK_TRACE(APP);
+}
+
+QPoint RKQWebView::scrollPosition() const {
+	RK_TRACE(APP);
+	return QPoint();
+}
+
+void RKQWebView::setScrollPosition(const QPoint &pos, bool wait_for_load) {
+	RK_TRACE(APP);
+}
+
+bool RKQWebView::supportsContentType(const QString &mimename) {
+	RK_TRACE(APP);
+	return true;
+}
+
+void RKQWebView::zoomIn() {
+	RK_TRACE(APP);
+}
+
+void RKQWebView::zoomOut() {
+	RK_TRACE(APP);
+}
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
new file mode 100644
index 000000000..078bed461
--- /dev/null
+++ b/rkward/windows/rkqwebview.h
@@ -0,0 +1,43 @@
+/*
+rkqwebview - This file is part of the RKWard project. Created: Sun Feb 08 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKQWEBVIEW_H
+#define RKQWEBVIEW_H
+
+#include <QPointer>
+
+#include "rkhtmlviewer.h"
+
+class QQuickWidget;
+
+class RKQWebView : public RKHTMLViewer {
+  public:
+	QWidget *createWidget(QWidget *parent) override;
+	QUrl url() const override;
+	void load(const QUrl &url) override;
+	void print() override;
+	void installPersistentJS(const QString &script, const QString &id) override;
+	void runJS(const QString &script, std::function<void(const QVariant &)> callback) override;
+	void setHTML(const QString &html, const QUrl &url) override;
+	bool installHelpProtocolHandler() override;
+	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
+	QMenu *createContextMenu(const QPoint &clickpos) override;
+	QString selectedText() const override;
+	void exportPage() override;
+	QPoint scrollPosition() const override;
+	void setScrollPosition(const QPoint &pos, bool wait_for_load) override;
+	bool supportsContentType(const QString &mimename) override;
+	void zoomIn() override;
+	void zoomOut() override;
+
+  private:
+	friend class RKHTMLViewer;
+	RKQWebView(RKHTMLWindow *parent);
+	QPointer<QQuickWidget> view;
+};
+
+#endif
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
new file mode 100644
index 000000000..761a1ea7e
--- /dev/null
+++ b/rkward/windows/rkqwebview.qml
@@ -0,0 +1,22 @@
+// rkqwebview - This file is part of the RKWard project. Created: Sun Feb 08 2026
+// SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+// SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+import QtQuick
+import QtQuick.Controls
+import QtWebView
+import QtQuick.Layouts
+
+Item {
+        id: frame
+        visible: true
+
+        WebView {
+                id: webView
+                objectName: "webView"
+                visible: true
+                width: parent.width
+                height: parent.height
+        }
+}


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Start_fixing_navigation?=
Message-ID: <20260409194424.62DCD14016E0@lerwini.kde.org>

Git commit 464fca86656f228a3b86bc397f18cd575d3b8eab by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Start fixing navigation

M  +1    -0    rkward/windows/rkhtmlviewer.h
M  +3    -0    rkward/windows/rkhtmlwindow.cpp
M  +44   -7    rkward/windows/rkqwebview.cpp
M  +5    -0    rkward/windows/rkqwebview.h
M  +14   -0    rkward/windows/rkqwebview.qml

https://invent.kde.org/education/rkward/-/commit/464fca86656f228a3b86bc397f18cd575d3b8eab

diff --git a/rkward/windows/rkhtmlviewer.h b/rkward/windows/rkhtmlviewer.h
index 7bf96a2a2..ea4270fc8 100644
--- a/rkward/windows/rkhtmlviewer.h
+++ b/rkward/windows/rkhtmlviewer.h
@@ -53,6 +53,7 @@ class RKHTMLViewer : public QObject {
 	void pageInternalNavigation(const QUrl &new_url);
 	void selectionChanged(bool has_selection);
 	void loadFinished();
+	void navigationRequest(const QUrl &current_real_url, const QUrl &requested_url, bool is_new_window);
 
   protected:
 	RKHTMLViewer(QObject *parent);
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index e1e0cf4e7..3e88a2a33 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -105,6 +105,9 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 
 	// We have to connect this in order to allow browsing.
 	connect(page, &RKHTMLViewer::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
+	connect(page, &RKHTMLViewer::navigationRequest, this, [this](const QUrl &current_real_url, const QUrl &requested_url, bool is_new_window) {
+		openURL(requested_url);
+	});
 
 	connect(view, &QWidget::customContextMenuRequested, this, &RKHTMLWindow::makeContextMenu);
 
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index df9462428..2c2f80ee8 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -15,8 +15,28 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
-RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent) {
-	RK_TRACE(APP);
+RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr) {
+	RK_TRACE(APP);
+}
+
+void RKQWebView::onPageLoad(const QUrl &_url, const QString &error, int status) {
+	RK_DEBUG(APP, DL_WARNING, "loading %s %d", qPrintable(_url.toString()), status);
+	/* TODO: we may need to inject a script similar for handling target=new
+	    and page internal navigation?
+	RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
+	    "  const origin = e.target.closest('a');"
+	    "  if (origin) alert(origin.href);"
+	    "})\n"_s); */
+	if (status == 0) {
+		bool new_window = false;
+		Q_EMIT navigationRequest(webView()->property("acceptedUrl").toUrl(), _url, new_window);
+		// we may be redirected via the above signal
+		if (_url != url()) {
+			RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
+		}
+	} else if (status == 2) {
+		Q_EMIT loadFinished();
+	}
 }
 
 QWidget *RKQWebView::createWidget(QWidget *parent) {
@@ -24,24 +44,40 @@ QWidget *RKQWebView::createWidget(QWidget *parent) {
 	view = new QQuickWidget(parent);
 	view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
 	view->setResizeMode(QQuickWidget::SizeRootObjectToView);
+	connect(webView(), SIGNAL(loaded(const QUrl &, const QString &, int)), this, SLOT(onPageLoad(const QUrl &, const QString &, int)));
 	return view;
 }
 
+QObject *RKQWebView::webView() const {
+	// TODO: we have some premature calls to this function. Find and fix them
+	if (!view) {
+		RK_ASSERT(view);
+		return nullptr;
+	}
+	if (view->status() != QQuickWidget::Ready) {
+		RK_ASSERT(view->status() == QQuickWidget::Ready);
+		return nullptr;
+	}
+	auto wv = view->rootObject()->findChild<QObject *>("webView");
+	RK_ASSERT(wv);
+	return wv;
+}
+
 QUrl RKQWebView::url() const {
 	RK_TRACE(APP);
-	return QUrl();
+	auto wv = webView();
+	return wv ? wv->property("url").toUrl() : QUrl();
 }
 
 void RKQWebView::load(const QUrl &url) {
 	RK_TRACE(APP);
-	auto wv = view->rootObject()->findChild<QObject *>("webView");
-	RK_ASSERT(wv);
-	if (!wv) return;
-	wv->setProperty("url", url);
+	webView()->setProperty("acceptedUrl", url);
+	webView()->setProperty("url", url);
 }
 
 void RKQWebView::print() {
 	RK_TRACE(APP);
+	RKHTMLViewer::runJS(u"window.print()"_s);
 }
 
 void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
@@ -50,6 +86,7 @@ void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
 
 void RKQWebView::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
 	RK_TRACE(APP);
+	QMetaObject::invokeMethod(webView(), "runJavaScript", Q_ARG(QString, script), Q_ARG(QJSValue, QJSValue()));
 }
 
 void RKQWebView::setHTML(const QString &html, const QUrl &url) {
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index 078bed461..e18780ac7 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -13,8 +13,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "rkhtmlviewer.h"
 
 class QQuickWidget;
+class QQuickItem;
 
 class RKQWebView : public RKHTMLViewer {
+	Q_OBJECT
   public:
 	QWidget *createWidget(QWidget *parent) override;
 	QUrl url() const override;
@@ -38,6 +40,9 @@ class RKQWebView : public RKHTMLViewer {
 	friend class RKHTMLViewer;
 	RKQWebView(RKHTMLWindow *parent);
 	QPointer<QQuickWidget> view;
+  private Q_SLOTS:
+	void onPageLoad(const QUrl &url, const QString &error, int status);
+	QObject *webView() const;
 };
 
 #endif
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
index 761a1ea7e..1059a24ab 100644
--- a/rkward/windows/rkqwebview.qml
+++ b/rkward/windows/rkqwebview.qml
@@ -13,10 +13,24 @@ Item {
         visible: true
 
         WebView {
+                signal loaded(url murl, string error, int status)
                 id: webView
                 objectName: "webView"
                 visible: true
                 width: parent.width
                 height: parent.height
+                property string acceptedUrl: ""
+
+                onLoadingChanged: request => {
+                        // give the frontend a chance to intervene, e.g. rewriting rkward:// url, opening in new window, 
+                        // denying external urls, etc.
+                        loaded(request.url, request.error, request.status);
+                        if (request.status == WebView.LoadStartedStatus) {
+                                if (request.url != acceptedUrl) {
+                                        //console.log("Navigation request to " + request.url + "denied");
+                                        url = acceptedUrl;
+                                }
+                        }
+                }
         }
 }


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Do_not_set_parent_during_QQuickWidget_construction=3A?=
Message-ID: <20260409194424.63BBA1401BAA@lerwini.kde.org>

Git commit 9abcc543dee56e6f3eeb76ec05585d9ea151ac4f by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Do not set parent during QQuickWidget construction:

It will force a premature showEvent on the parent

M  +1    -1    rkward/windows/rkhtmlviewer.h
M  +1    -1    rkward/windows/rkhtmlwindow.cpp
M  +2    -2    rkward/windows/rkqwebenginewidget.cpp
M  +1    -1    rkward/windows/rkqwebenginewidget.h
M  +2    -2    rkward/windows/rkqwebview.cpp
M  +1    -1    rkward/windows/rkqwebview.h

https://invent.kde.org/education/rkward/-/commit/9abcc543dee56e6f3eeb76ec05585d9ea151ac4f

diff --git a/rkward/windows/rkhtmlviewer.h b/rkward/windows/rkhtmlviewer.h
index ea4270fc8..00fdc4c23 100644
--- a/rkward/windows/rkhtmlviewer.h
+++ b/rkward/windows/rkhtmlviewer.h
@@ -22,7 +22,7 @@ class RKHTMLViewer : public QObject {
 	Q_OBJECT
   public:
 	static RKHTMLViewer *getNew(RKHTMLWindow *parent);
-	virtual QWidget *createWidget(QWidget *parent) = 0;
+	virtual QWidget *createWidget() = 0;
 	virtual void reload() {
 		load(url());
 	}
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 3e88a2a33..a6d5d3984 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -71,7 +71,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	QVBoxLayout *layout = new QVBoxLayout(this);
 	layout->setContentsMargins(0, 0, 0, 0);
 	page = RKHTMLViewer::getNew(this);
-	auto view = page->createWidget(this);
+	auto view = page->createWidget();
 	view->setContextMenuPolicy(Qt::CustomContextMenu);
 	layout->addWidget(view, 1);
 	findbar = new RKFindBar(this, true);
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
index 29c2795a8..e345f2915 100644
--- a/rkward/windows/rkqwebenginewidget.cpp
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -215,7 +215,7 @@ RKQWebEngineWidget::RKQWebEngineWidget(RKHTMLWindow *parent) : RKHTMLViewer(pare
 	}
 }
 
-QWidget *RKQWebEngineWidget::createWidget(QWidget *parent) {
+QWidget *RKQWebEngineWidget::createWidget() {
 	RK_TRACE(APP);
 
 	// We keep our own history.
@@ -245,7 +245,7 @@ QWidget *RKQWebEngineWidget::createWidget(QWidget *parent) {
 	});
 
 	RK_ASSERT(!view);
-	view = new QWebEngineView(page, parent);
+	view = new QWebEngineView(page);
 	connect(view, &QWebEngineView::selectionChanged, this, [this]() {
 		Q_EMIT selectionChanged(view->hasSelection());
 	});
diff --git a/rkward/windows/rkqwebenginewidget.h b/rkward/windows/rkqwebenginewidget.h
index 297eba049..205cc6308 100644
--- a/rkward/windows/rkqwebenginewidget.h
+++ b/rkward/windows/rkqwebenginewidget.h
@@ -17,7 +17,7 @@ class QWebEngineView;
 
 class RKQWebEngineWidget : public RKHTMLViewer {
   public:
-	QWidget *createWidget(QWidget *parent) override;
+	QWidget *createWidget() override;
 	void reload() override;
 	QUrl url() const override;
 	void load(const QUrl &url) override;
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 2c2f80ee8..1d40cd158 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -39,9 +39,9 @@ void RKQWebView::onPageLoad(const QUrl &_url, const QString &error, int status)
 	}
 }
 
-QWidget *RKQWebView::createWidget(QWidget *parent) {
+QWidget *RKQWebView::createWidget() {
 	RK_TRACE(APP);
-	view = new QQuickWidget(parent);
+	view = new QQuickWidget();
 	view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
 	view->setResizeMode(QQuickWidget::SizeRootObjectToView);
 	connect(webView(), SIGNAL(loaded(const QUrl &, const QString &, int)), this, SLOT(onPageLoad(const QUrl &, const QString &, int)));
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index e18780ac7..48511ed09 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -18,7 +18,7 @@ class QQuickItem;
 class RKQWebView : public RKHTMLViewer {
 	Q_OBJECT
   public:
-	QWidget *createWidget(QWidget *parent) override;
+	QWidget *createWidget() override;
 	QUrl url() const override;
 	void load(const QUrl &url) override;
 	void print() override;


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward=3A_Add_abstraction_layer_over_html_renderer_engine_to_use?=
Message-ID: <20260409194424.5D6D51400299@lerwini.kde.org>

Git commit 75f01c0ea3585e706e36f49a7f06f94f29ed22bb by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Add abstraction layer over html renderer engine to use

M  +1    -1    rkward/misc/rkfindbar.h
M  +2    -0    rkward/windows/CMakeLists.txt
A  +23   -0    rkward/windows/rkhtmlviewer.cpp     [License: GPL(v2.0+)]
A  +62   -0    rkward/windows/rkhtmlviewer.h     [License: GPL(v2.0+)]
M  +47   -290  rkward/windows/rkhtmlwindow.cpp
M  +3    -10   rkward/windows/rkhtmlwindow.h
M  +0    -1    rkward/windows/rkoutputwindow.rc
A  +379  -0    rkward/windows/rkqwebenginewidget.cpp     [License: GPL(v2.0+)]
A  +46   -0    rkward/windows/rkqwebenginewidget.h     [License: GPL(v2.0+)]

https://invent.kde.org/education/rkward/-/commit/75f01c0ea3585e706e36f49a7f06f94f29ed22bb

diff --git a/rkward/misc/rkfindbar.h b/rkward/misc/rkfindbar.h
index 1f36c707a..e64d4fd35 100644
--- a/rkward/misc/rkfindbar.h
+++ b/rkward/misc/rkfindbar.h
@@ -42,7 +42,7 @@ class RKFindBar : public QWidget {
 	void forward();
 	void backward();
   Q_SIGNALS:
-	void findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *result);
+	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *result);
   private Q_SLOTS:
 	/** search term _or_ search options changed. Triggers a forward search, if FindAsYouType is active */
 	void searchChanged();
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index 688206baf..d3bb67b98 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -11,6 +11,8 @@ SET(windows_STAT_SRCS
 	rkdebugconsole.cpp
 	rkcallstackviewer.cpp
 	rkhtmlwindow.cpp
+	rkhtmlviewer.cpp
+	rkqwebenginewidget.cpp
 	rkpdfwindow.cpp
 	rcontrolwindow.cpp
 	detachedwindowcontainer.cpp
diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
new file mode 100644
index 000000000..058ab113c
--- /dev/null
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -0,0 +1,23 @@
+/*
+rkhtmlviewerwidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkhtmlviewer.h"
+
+#include "rkqwebenginewidget.h"
+
+#include "../debug.h"
+
+RKHTMLViewer::RKHTMLViewer(QObject *parent) : QObject(parent) {
+	RK_TRACE(APP);
+}
+
+RKHTMLViewer *RKHTMLViewer::getNew(RKHTMLWindow *parent) {
+	RK_TRACE(APP);
+	return new RKQWebEngineWidget(parent);
+}
+
+#include "rkhtmlviewer.moc"
diff --git a/rkward/windows/rkhtmlviewer.h b/rkward/windows/rkhtmlviewer.h
new file mode 100644
index 000000000..7bf96a2a2
--- /dev/null
+++ b/rkward/windows/rkhtmlviewer.h
@@ -0,0 +1,62 @@
+/*
+rkhtmlviewerwidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKHTMLVIEWERWIDGET_H
+#define RKHTMLVIEWERWIDGET_H
+
+#include <QObject>
+#include <QString>
+#include <QUrl>
+
+class QMenu;
+class QWidget;
+class RKFindBar;
+class RKHTMLWindow;
+
+/** Abstract base class for the HTML viewer component */
+class RKHTMLViewer : public QObject {
+	Q_OBJECT
+  public:
+	static RKHTMLViewer *getNew(RKHTMLWindow *parent);
+	virtual QWidget *createWidget(QWidget *parent) = 0;
+	virtual void reload() {
+		load(url());
+	}
+	virtual QUrl url() const = 0;
+	virtual void load(const QUrl &url) = 0;
+	virtual void exportPage() = 0;
+	virtual void print() = 0;
+	virtual void installPersistentJS(const QString &script, const QString &id) = 0;
+	virtual void runJS(const QString &script, std::function<void(const QVariant &)> callback) = 0;
+	void runJS(const QString &script) {
+		runJS(script, [](const QVariant &) {});
+	}
+	virtual void setHTML(const QString &html, const QUrl &url) = 0;
+	virtual bool installHelpProtocolHandler() {
+		return false;
+	}
+	virtual QPoint scrollPosition() const = 0;
+	virtual void setScrollPosition(const QPoint &pos, bool wait_for_load) = 0;
+	// TODO: Could be implemented in base-class as JS call
+	virtual void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) = 0;
+	virtual QMenu *createContextMenu(const QPoint &clickpos) = 0;
+	virtual QString selectedText() const = 0;
+	virtual bool supportsContentType(const QString &mimename) = 0;
+	virtual void zoomIn() = 0;
+	virtual void zoomOut() = 0;
+  Q_SIGNALS:
+	// TODO: code to emit these signals
+	void pageInternalNavigation(const QUrl &new_url);
+	void selectionChanged(bool has_selection);
+	void loadFinished();
+
+  protected:
+	RKHTMLViewer(QObject *parent);
+	RKHTMLWindow *window;
+};
+
+#endif
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index ff99418cf..e1e0cf4e7 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -20,9 +20,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include <QBuffer>
 #include <QCheckBox>
+#include <QClipboard>
 #include <QDir>
 #include <QFileDialog>
-#include <QFontDatabase>
 #include <QGuiApplication>
 #include <QHBoxLayout>
 #include <QHostInfo>
@@ -30,19 +30,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QMenu>
 #include <QMimeDatabase>
 #include <QPrintDialog>
-#include <QStringEncoder>
 #include <QTemporaryFile>
 #include <QTimer>
-#include <QWebEngineFindTextResult>
-#include <QWebEnginePage>
-#include <QWebEngineProfile>
-#include <QWebEngineScript>
-#include <QWebEngineScriptCollection>
-#include <QWebEngineSettings>
-#include <QWebEngineUrlRequestJob>
-#include <QWebEngineUrlSchemeHandler>
-#include <QWebEngineView>
-#include <QWheelEvent>
 #include <qfileinfo.h>
 #include <qlayout.h>
 #include <qwidget.h>
@@ -66,6 +55,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include "../settings/rksettings.h"
 #include "../settings/rksettingsmoduleoutput.h"
 #include "../settings/rksettingsmoduler.h"
+#include "../windows/rkhtmlviewer.h"
 #include "../windows/rkworkplace.h"
 #include "../windows/rkworkplaceview.h"
 #include "rkhelpsearchwindow.h"
@@ -74,190 +64,18 @@ QUrl restorableUrl(const QUrl &url) {
 	return QUrl(url.url().replace(RKSettingsModuleR::helpBaseUrl(), QLatin1String("rkward://RHELPBASE")));
 }
 
-class RKWebPage : public QWebEnginePage {
-	Q_OBJECT
-  public:
-	explicit RKWebPage(RKHTMLWindow *window) : QWebEnginePage(window) {
-		RK_TRACE(APP);
-		RKWebPage::window = window;
-		direct_load = false;
-		settings()->setFontFamily(QWebEngineSettings::StandardFont, QFontDatabase::systemFont(QFontDatabase::GeneralFont).family());
-		settings()->setFontFamily(QWebEngineSettings::FixedFont, QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
-	}
-
-	void load(const QUrl &url) {
-		RK_TRACE(APP);
-		direct_load = true;
-		QWebEnginePage::load(url);
-	}
-
-	void setHtmlWrapper(const QString &html, const QUrl &baseurl) {
-		direct_load = true;
-		setHtml(html, baseurl);
-	}
-	bool supportsContentType(const QString &name) {
-		if (name.startsWith(QLatin1String("text"))) return true;
-		return false;
-	}
-	void downloadUrl(const QUrl &url) {
-		download(url);
-	}
-	void setScrollPosition(const QPoint &point) {
-		RK_DEBUG(APP, DL_DEBUG, "scrolling to %d, %d", point.x(), point.y());
-		runJavaScript(QStringLiteral("window.scrollTo(%1, %2);").arg(point.x()).arg(point.y()));
-	}
-	void setScrollPositionWhenDone(const QPoint &pos) {
-		QMetaObject::Connection *const connection = new QMetaObject::Connection;
-		*connection = connect(this, &QWebEnginePage::loadFinished, [this, pos, connection]() {
-			QObject::disconnect(*connection);
-			delete connection;
-			setScrollPosition(pos);
-		});
-	}
-
-  Q_SIGNALS:
-	void pageInternalNavigation(const QUrl &url);
-
-  protected:
-	bool acceptNavigationRequest(const QUrl &navurl, QWebEnginePage::NavigationType type, bool is_main_frame) override {
-		QUrl cururl(url());
-		Q_UNUSED(type);
-
-		RK_TRACE(APP);
-		RK_DEBUG(APP, DL_DEBUG, "Navigation request to %s", qPrintable(navurl.toString()));
-		if (direct_load && (is_main_frame)) {
-			direct_load = false;
-			return true;
-		}
-
-		if (RKHTMLWindow::new_window) {
-			RK_ASSERT(RKHTMLWindow::new_window == this);
-			RK_ASSERT(!window);
-			RKWorkplace::mainWorkplace()->openAnyUrl(restorableUrl(navurl));
-			RKHTMLWindow::new_window = nullptr;
-			if (!window) deleteLater(); // this page was _not_ reused
-			return false;
-		}
-		RK_ASSERT(window);
-
-		if (!is_main_frame) {
-			if (navurl.isLocalFile() && supportsContentType(QMimeDatabase().mimeTypeForUrl(navurl).name())) return true;
-		}
-
-		if (cururl.matches(navurl, QUrl::NormalizePathSegments | QUrl::StripTrailingSlash)) {
-			RK_DEBUG(APP, DL_DEBUG, "Page internal navigation request from %s to %s", qPrintable(cururl.toString()), qPrintable(navurl.toString()));
-			Q_EMIT pageInternalNavigation(navurl);
-			return true;
-		}
-
-		window->openURL(navurl);
-		return false;
-	}
-
-	QWebEnginePage *createWindow(QWebEnginePage::WebWindowType) override {
-		RK_TRACE(APP);
-		RKWebPage *ret = new RKWebPage(nullptr);
-		RKHTMLWindow::new_window = ret; // Don't actually create a full window, until we know which URL we're talking about.
-		// sigh: acceptNavigationRequest() does  not get called on the new page...
-		QMetaObject::Connection *const connection = new QMetaObject::Connection;
-		*connection = connect(ret, &RKWebPage::loadStarted, [ret, connection
-#ifdef _MSC_VER
-		                                                     ,
-		                                                     this // capturing "this" makes MSVC happy
-#endif
-		]() {
-			QObject::disconnect(*connection);
-			delete connection;
-			ret->acceptNavigationRequest(ret->url(), QWebEnginePage::NavigationTypeLinkClicked, true);
-		});
-		return (ret);
-	}
-
-	friend class RKHTMLWindow;
-	RKHTMLWindow *window;
-	bool direct_load;
-};
-
-class RKWebView : public QWebEngineView {
-  public:
-	explicit RKWebView(QWidget *parent) : QWebEngineView(parent) {};
-	void print(QPrinter *printer) {
-		if (!page()) return;
-		QWebEngineView::forPage(page())->print(printer);
-	};
-
-  protected:
-	bool eventFilter(QObject *, QEvent *event) override {
-		if (event->type() == QEvent::Wheel) {
-			QWheelEvent *we = static_cast<QWheelEvent *>(event);
-			if (we->modifiers() & Qt::ControlModifier) {
-				setZoomFactor(zoomFactor() + we->angleDelta().y() / 1200.0);
-				return true;
-			}
-		}
-		return false;
-	}
-	void childEvent(QChildEvent *event) override {
-		if (event->type() == QChildEvent::ChildAdded) {
-			event->child()->installEventFilter(this);
-		}
-	}
-	// NOTE: Code below won't work, due to https://bugreports.qt.io/browse/QTBUG-43602
-	/*	void wheelEvent (QWheelEvent *event) override {
-	        [handle zooming]
-	    } */
-};
-
-class RKWebEngineKIOForwarder : public QWebEngineUrlSchemeHandler {
-  public:
-	explicit RKWebEngineKIOForwarder(QObject *parent) : QWebEngineUrlSchemeHandler(parent) {}
-	void requestStarted(QWebEngineUrlRequestJob *request) override {
-		RK_DEBUG(APP, DL_DEBUG, "new KIO request to %s", qPrintable(request->requestUrl().url()));
-		KIO::StoredTransferJob *job = KIO::storedGet(request->requestUrl(), KIO::NoReload, KIO::HideProgressInfo);
-		connect(job, &KIO::StoredTransferJob::result, this, [this, job]() { kioJobFinished(job); });
-		jobs.insert(job, request);
-	}
-
-  private:
-	void kioJobFinished(KIO::StoredTransferJob *job) {
-		QWebEngineUrlRequestJob *request = jobs.take(job);
-		if (!request) {
-			return;
-		}
-		if (job->error()) {
-			request->fail(QWebEngineUrlRequestJob::UrlInvalid); // TODO
-			return;
-		}
-		QBuffer *buf = new QBuffer(request);
-		buf->setData(job->data());
-		request->reply(QMimeDatabase().mimeTypeForData(job->data()).name().toUtf8(), buf);
-	}
-	QMap<KIO::StoredTransferJob *, QPointer<QWebEngineUrlRequestJob>> jobs;
-};
-
-RKWebPage *RKHTMLWindow::new_window = nullptr;
 RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(parent, RKMDIWindow::HelpWindow) {
 	RK_TRACE(APP);
 
 	current_cache_file = nullptr;
 	QVBoxLayout *layout = new QVBoxLayout(this);
 	layout->setContentsMargins(0, 0, 0, 0);
-	view = new RKWebView(this);
-	if (new_window) {
-		page = new_window;
-		page->window = this;
-		new_window = nullptr;
-	} else {
-		page = new RKWebPage(this);
-	}
-	view->setPage(page);
+	page = RKHTMLViewer::getNew(this);
+	auto view = page->createWidget(this);
 	view->setContextMenuPolicy(Qt::CustomContextMenu);
 	layout->addWidget(view, 1);
 	findbar = new RKFindBar(this, true);
 	findbar->setPrimaryOptions(QList<QWidget *>() << findbar->getOption(RKFindBar::FindAsYouType) << findbar->getOption(RKFindBar::MatchCase));
-	if (!QWebEngineProfile::defaultProfile()->urlSchemeHandler("help")) {
-		QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("help", new RKWebEngineKIOForwarder(RKWardMainWindow::getMain()));
-	}
 	// Apply current color scheme to page. This needs support in the CSS of the page, so will only work for RKWard help and output pages.
 	// Note that the CSS in those pages also has "automatic" support for dark mode ("prefers-color-scheme: dark"; but see https://bugreports.qt.io/browse/QTBUG-89753), however,
 	// for a seamless appearance, the only option is to set the theme colors dynamically, via javascript.
@@ -271,23 +89,12 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	                              .arg(scheme->foreground().color().name(), scheme->background().color().name(),
 	                                   scheme->foreground(KColorScheme::VisitedText).color().name(), scheme->foreground(KColorScheme::LinkText).color().name(),
 	                                   scheme->shade(KColorScheme::MidShade).name());
-	auto p = QWebEngineProfile::defaultProfile();
-	QWebEngineScript fix_color_scheme;
-	const QString id = QStringLiteral("fix_color_scheme");
-	const auto scripts = p->scripts()->find(id);
-	for (const auto &script : scripts) {
-		p->scripts()->remove(script); // remove any existing variant of the script. It might have been created for the wrong theme.
-	}
-	fix_color_scheme.setName(id);
-	fix_color_scheme.setInjectionPoint(QWebEngineScript::DocumentReady);
-	fix_color_scheme.setSourceCode(color_scheme_js);
-	p->scripts()->insert(fix_color_scheme);
-	// page->setBackgroundColor(scheme.background().color());  // avoids brief white blink while loading, but is too risky on pages that are not dark scheme aware.
+	page->installPersistentJS(u"fix_color_scheme"_s, color_scheme_js);
+	page->installHelpProtocolHandler();
 
 	layout->addWidget(findbar);
 	findbar->hide();
-	connect(findbar, &RKFindBar::findRequest, this, &RKHTMLWindow::findRequest);
-	have_highlight = false;
+	connect(findbar, &RKFindBar::findRequest, page, &RKHTMLViewer::findRequest);
 
 	part = new RKHTMLWindowPart(this);
 	setPart(part);
@@ -297,19 +104,8 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	setFocusProxy(view);
 
 	// We have to connect this in order to allow browsing.
-	connect(page, &RKWebPage::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
-	connect(page->profile(), &QWebEngineProfile::downloadRequested, this, [this](QWebEngineDownloadRequest *item) {
-		QString defpath;
-		defpath = QDir(item->downloadDirectory()).absoluteFilePath(item->downloadFileName());
-		QString path = QFileDialog::getSaveFileName(this, i18n("Save as"), defpath);
-		if (path.isEmpty()) return;
-		QFileInfo fi(path);
-		item->setDownloadDirectory(fi.absolutePath());
-		item->setDownloadFileName(fi.fileName());
-		item->accept();
-	});
+	connect(page, &RKHTMLViewer::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
 
-	connect(page, &RKWebPage::printRequested, this, &RKHTMLWindow::slotPrint);
 	connect(view, &QWidget::customContextMenuRequested, this, &RKHTMLWindow::makeContextMenu);
 
 	current_history_position = -1;
@@ -319,7 +115,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	useMode(mode);
 
 	// needed to enable / disable the run selection action
-	connect(view, &RKWebView::selectionChanged, this, &RKHTMLWindow::selectionChanged);
+	connect(page, &RKHTMLViewer::selectionChanged, this, &RKHTMLWindow::selectionChanged);
 	selectionChanged();
 }
 
@@ -337,13 +133,13 @@ QUrl RKHTMLWindow::restorableUrl() {
 void RKHTMLWindow::makeContextMenu(const QPoint &pos) {
 	RK_TRACE(APP);
 
-	QMenu *menu = QWebEngineView::forPage(page)->createStandardContextMenu();
+	auto menu = page->createContextMenu(pos);
 	menu->addAction(part->run_selection);
-	menu->exec(view->mapToGlobal(pos));
+	menu->exec(mapToGlobal(pos));
 	delete (menu);
 }
 
-void RKHTMLWindow::selectionChanged() {
+void RKHTMLWindow::selectionChanged(bool have_selection) {
 	RK_TRACE(APP);
 
 	if (!(part && part->run_selection)) {
@@ -351,46 +147,24 @@ void RKHTMLWindow::selectionChanged() {
 		return;
 	}
 
-	part->run_selection->setEnabled(view->hasSelection());
+	part->run_selection->setEnabled(have_selection);
 }
 
 void RKHTMLWindow::runSelection() {
 	RK_TRACE(APP);
 
-	RKConsole::pipeUserCommand(view->selectedText());
-}
-
-void RKHTMLWindow::findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *found) {
-	RK_TRACE(APP);
-
-	// QWebEngine does not offer highlight all
-	*found = true;
-	QWebEnginePage::FindFlags flags;
-	if (backwards) flags |= QWebEnginePage::FindBackward;
-	if (findbar->isOptionSet(RKFindBar::MatchCase)) flags |= QWebEnginePage::FindCaseSensitively;
-	page->findText(text, flags, [this](QWebEngineFindTextResult result) {
-		if (result.numberOfMatches() == 0) {
-			this->findbar->indicateSearchFail();
-		}
-	});
+	RKConsole::pipeUserCommand(page->selectedText());
 }
 
 void RKHTMLWindow::slotPrint() {
 	RK_TRACE(APP);
-
-	// NOTE: taken from kwebkitpart, with small mods
-	// Make it non-modal, in case a redirection deletes the part
-	QPointer<QPrintDialog> dlg(new QPrintDialog(view));
-	if (dlg->exec() == QPrintDialog::Accepted) {
-		view->print(dlg->printer());
-	}
-	delete dlg;
+	page->print();
 }
 
 void RKHTMLWindow::slotExport() {
 	RK_TRACE(APP);
 
-	page->downloadUrl(page->url());
+	page->exportPage();
 }
 
 void RKHTMLWindow::slotSave() {
@@ -426,11 +200,11 @@ void RKHTMLWindow::openLocationFromHistory(VisitedLocation &loc) {
 	RK_ASSERT(current_history_position <= history_last);
 	QPoint scroll_pos = loc.scroll_position.toPoint();
 	if (loc.url == current_url) {
-		page->setScrollPosition(scroll_pos);
+		page->setScrollPosition(scroll_pos, false);
 	} else {
 		url_change_is_from_history = true;
-		openURL(loc.url); // TODO: merge into restoreBrowserState()?
-		page->setScrollPositionWhenDone(scroll_pos);
+		openURL(loc.url);
+		page->setScrollPosition(scroll_pos, true);
 		url_change_is_from_history = false;
 	}
 
@@ -556,7 +330,7 @@ bool RKHTMLWindow::handleRKWardURL(const QUrl &url, RKHTMLWindow *window) {
 
 void RKHTMLWindow::setContent(const QString &content) {
 	RK_TRACE(APP);
-	page->setHtmlWrapper(content, QUrl());
+	page->setHTML(content, QUrl());
 }
 
 bool RKHTMLWindow::openURL(const QUrl &url) {
@@ -565,7 +339,7 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
 	if (handleRKWardURL(url, this)) return true;
 
 	QPoint restore_position;
-	if (url == current_url) restore_position = page->scrollPosition().toPoint();
+	if (url == current_url) restore_position = page->scrollPosition();
 
 	QMimeType mtype = QMimeDatabase().mimeTypeForUrl(url);
 	if (window_mode == HTMLOutputWindow) {
@@ -595,7 +369,7 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
 				RK_DEBUG(APP, DL_WARNING, "Applying workaround for https://bugs.kde.org/show_bug.cgi?id=405386");
 				QFile f(url.toLocalFile());
 				RK_ASSERT(f.open(QIODevice::ReadOnly));
-				page->setHtmlWrapper(QString::fromUtf8(f.readAll()), url.adjusted(QUrl::RemoveFilename));
+				page->setHTML(QString::fromUtf8(f.readAll()), url.adjusted(QUrl::RemoveFilename));
 				f.close();
 			} else {
 				// NOTE: Quirk in Qt 6.7: When first loading a page, the window is somehow brought to the front, again. In preview windows
@@ -604,11 +378,11 @@ bool RKHTMLWindow::openURL(const QUrl &url) {
 				//       With this, the flicker is still there, but feels more "natural"
 				if (isVisible()) page->load(url);
 			}
-			if (!restore_position.isNull()) page->setScrollPositionWhenDone(restore_position);
+			if (!restore_position.isNull()) page->setScrollPosition(restore_position, true);
 		} else {
 			RK_DEBUG(APP, DL_WARNING, "Attempt to open non-existant local file %s", qPrintable(url.toLocalFile()));
 			if (window_mode == HTMLOutputWindow) {
-				page->setHtmlWrapper(QStringLiteral("<HTML><BODY><H1>") + i18n("RKWard output file %s does not (yet) exist").arg(url.toLocalFile()) + QStringLiteral("</H1>\n</BODY></HTML>"), QUrl());
+				page->setHTML(QStringLiteral("<HTML><BODY><H1>") + i18n("RKWard output file %1 does not (yet) exist").arg(url.toLocalFile()) + QStringLiteral("</H1>\n</BODY></HTML>"), QUrl());
 			} else {
 				fileDoesNotExistMessage();
 			}
@@ -765,8 +539,8 @@ void RKHTMLWindow::refresh() {
 		openRKHPage(url());
 	} else {
 		// NOTE: Special handling for output window, which may not have existed at the time of first "load"
-		if (!view->url().isEmpty()) view->reload();
-		else view->load(url());
+		if (!page->url().isEmpty()) page->reload();
+		else page->load(url());
 	}
 }
 
@@ -774,25 +548,17 @@ void RKHTMLWindow::scrollToBottom() {
 	RK_TRACE(APP);
 
 	RK_ASSERT(window_mode == HTMLOutputWindow);
-	page->runJavaScript(QStringLiteral("{ let se = (document.scrollingElement || document.body); se.scrollTop = se.scrollHeight; }"));
+	page->runJS(QStringLiteral("{ let se = (document.scrollingElement || document.body); se.scrollTop = se.scrollHeight; }"));
 }
 
 void RKHTMLWindow::zoomIn() {
 	RK_TRACE(APP);
-	view->setZoomFactor(view->zoomFactor() * 1.1);
+	page->zoomIn();
 }
 
 void RKHTMLWindow::zoomOut() {
 	RK_TRACE(APP);
-	view->setZoomFactor(view->zoomFactor() / 1.1);
-}
-
-void RKHTMLWindow::setTextEncoding(QStringConverter::Encoding encoding) {
-	RK_TRACE(APP);
-
-	QStringEncoder converter(encoding);
-	page->settings()->setDefaultTextEncoding(QString::fromUtf8(converter.name()));
-	view->reload();
+	page->zoomOut();
 }
 
 void RKHTMLWindow::useMode(WindowMode new_mode) {
@@ -805,8 +571,8 @@ void RKHTMLWindow::useMode(WindowMode new_mode) {
 		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowOutput));
 		part->setOutputWindowSkin();
 		setMetaInfo(i18n("Output Window"), QUrl(QStringLiteral("rkward://page/rkward_output")), RKSettingsModuleOutput::page_id);
-		connect(page, &RKWebPage::loadFinished, this, &RKHTMLWindow::scrollToBottom);
-		page->action(RKWebPage::Reload)->setText(i18n("&Refresh Output"));
+		connect(page, &RKHTMLViewer::loadFinished, this, &RKHTMLWindow::scrollToBottom);
+		part->outputRefresh->setText(i18n("&Refresh Output"));
 
 		//	TODO: This would be an interesting extension, but how to deal with concurrent edits?
 		//		page->setContentEditable (true);
@@ -816,7 +582,7 @@ void RKHTMLWindow::useMode(WindowMode new_mode) {
 		type = RKMDIWindow::HelpWindow | RKMDIWindow::DocumentWindow;
 		setWindowIcon(RKStandardIcons::getIcon(RKStandardIcons::WindowHelp));
 		part->setHelpWindowSkin();
-		disconnect(page, &RKWebPage::loadFinished, this, &RKHTMLWindow::scrollToBottom);
+		disconnect(page, &RKHTMLViewer::loadFinished, this, &RKHTMLWindow::scrollToBottom);
 	}
 
 	updateCaption(current_url);
@@ -832,7 +598,7 @@ void RKHTMLWindow::startNewCacheFile() {
 void RKHTMLWindow::fileDoesNotExistMessage() {
 	RK_TRACE(APP);
 
-	page->setHtmlWrapper(u"<html><body><h1>"_s + i18n("Page does not exist or is broken") + u"</h1></body></html>"_s, QUrl());
+	page->setHTML(u"<html><body><h1>"_s + i18n("Page does not exist or is broken") + u"</h1></body></html>"_s, QUrl());
 }
 
 void RKHTMLWindow::flushOutput() {
@@ -878,34 +644,23 @@ RKHTMLWindowPart::RKHTMLWindowPart(RKHTMLWindow *window) : KParts::Part(window)
 void RKHTMLWindowPart::initActions() {
 	RK_TRACE(APP);
 
-	// We keep our own history.
-	window->page->action(RKWebPage::Back)->setVisible(false);
-	window->page->action(RKWebPage::Forward)->setVisible(false);
-	// For now we won't bother with this one: Does not behave well, in particular (but not only) WRT to rkward://-links
-	window->page->action(RKWebPage::DownloadLinkToDisk)->setVisible(false);
-	// Not really useful for us, and cannot easily be made to work, as all new pages go through RKWorkplace::openAnyUrl()
-	window->page->action(RKWebPage::ViewSource)->setVisible(false);
-	// Well, technically, all our windows are tabs, but we're calling them "window".
-	// At any rate, we don't need both "open link in new tab" and "open link in new window".
-	window->page->action(RKWebPage::OpenLinkInNewTab)->setVisible(false);
-
 	// common actions
-	actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window->view->pageAction(RKWebPage::Copy), &QAction::trigger);
+	actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window->page, [page = window->page]() {
+		page->runJS(u"{ Window.getSelection(); }"_s, [](const QVariant &res) {
+			const auto txt = res.toString();
+			if (!txt.isEmpty()) {
+				qApp->clipboard()->setText(txt);
+			}
+		});
+	});
 	QAction *zoom_in = actionCollection()->addAction(QStringLiteral("zoom_in"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-in")), i18n("Zoom In"), this));
 	connect(zoom_in, &QAction::triggered, window, &RKHTMLWindow::zoomIn);
 	QAction *zoom_out = actionCollection()->addAction(QStringLiteral("zoom_out"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), i18n("Zoom Out"), this));
 	connect(zoom_out, &QAction::triggered, window, &RKHTMLWindow::zoomOut);
-	actionCollection()->addAction(KStandardAction::SelectAll, QStringLiteral("select_all"), window->view->pageAction(RKWebPage::SelectAll), &QAction::trigger);
-	// unfortunately, this will only affect the default encoding, not necessarily the "real" encoding
-	KCodecAction *encoding = new KCodecAction(QIcon::fromTheme(QStringLiteral("character-set")), i18n("Default &Encoding"), this, true);
-	encoding->setWhatsThis(i18n("Set the encoding to assume in case no explicit encoding has been set in the page or in the HTTP headers."));
-	actionCollection()->addAction(QStringLiteral("view_encoding"), encoding);
-	connect(encoding, &KCodecAction::codecNameTriggered, window, [this](const QByteArray &name) {
-		auto encoding = QStringConverter::encodingForName(name.constData());
-		if (encoding) {
-			window->setTextEncoding(encoding.value());
-		}
+	actionCollection()->addAction(KStandardAction::SelectAll, QStringLiteral("select_all"), window->page, [page = window->page]() {
+		page->runJS(u"{ Document.selectAll(document); }"_s);
 	});
+	// TODO: We used to have an encoding action. Did not seem worth the trouble to port
 
 	print = actionCollection()->addAction(KStandardAction::Print, QStringLiteral("print_html"), window, &RKHTMLWindow::slotPrint);
 	export_page = actionCollection()->addAction(QStringLiteral("save_html"), new QAction(QIcon::fromTheme(QStringLiteral("file-save")), i18n("Export Page as HTML"), this));
@@ -930,7 +685,9 @@ void RKHTMLWindowPart::initActions() {
 	outputFlush->setText(i18n("&Clear Output"));
 	outputFlush->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete")));
 
-	outputRefresh = actionCollection()->addAction(QStringLiteral("output_refresh"), window->page->action(RKWebPage::Reload));
+	outputRefresh = actionCollection()->addAction(QStringLiteral("output_refresh"), window->page, &RKHTMLViewer::reload);
+	outputRefresh->setText(i18n("Reload"));
+	outputRefresh->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
 
 	revert = actionCollection()->addAction(QStringLiteral("output_revert"), window, &RKHTMLWindow::slotRevert);
 	revert->setText(i18n("&Revert to last saved state"));
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 54ca96951..51f3edcd6 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -30,8 +30,7 @@ class QTemporaryFile;
 class RKHTMLWindow;
 class RKFindBar;
 class RCommandChain;
-class RKWebPage;
-class RKWebView;
+class RKHTMLViewer;
 class RKOutputDirectory;
 
 /**
@@ -84,7 +83,7 @@ class RKHTMLWindow : public RKMDIWindow {
 	void slotActivate();
 	void slotForward();
 	void slotBack();
-	void selectionChanged();
+	void selectionChanged(bool have_selection = false);
 	void runSelection();
 	/** flush current output. */
 	void flushOutput();
@@ -92,7 +91,6 @@ class RKHTMLWindow : public RKMDIWindow {
 	void refresh();
 	void zoomIn();
 	void zoomOut();
-	void setTextEncoding(QStringConverter::Encoding encoding);
 	void updateState();
   private Q_SLOTS:
 	void scrollToBottom();
@@ -101,14 +99,11 @@ class RKHTMLWindow : public RKMDIWindow {
 	void mimeTypeJobFail2(KJob *);
 	void internalNavigation(const QUrl &new_url);
 	void makeContextMenu(const QPoint &pos);
-	void findRequest(const QString &text, bool backwards, const RKFindBar *findbar, bool *found);
 
   private:
 	friend class RKHTMLWindowPart;
-	RKWebView *view;
-	RKWebPage *page;
+	RKHTMLViewer *page;
 	RKFindBar *findbar;
-	bool have_highlight;
 	/** In case the part is a khtmlpart: A ready-cast pointer to that. 0 otherwise (if a webkit part is in use) */
 	RKHTMLWindowPart *part;
 	/** update caption according to given URL */
@@ -138,8 +133,6 @@ class RKHTMLWindow : public RKMDIWindow {
 	void saveBrowserState(VisitedLocation *state);
 	/** the RKOutpuDirectory viewed in this window (if any) */
 	RKOutputDirectory *dir;
-	friend class RKWebPage;
-	static RKWebPage *new_window;
 };
 
 class RKHTMLWindowPart : public KParts::Part {
diff --git a/rkward/windows/rkoutputwindow.rc b/rkward/windows/rkoutputwindow.rc
index 124888cbd..ce355acd8 100644
--- a/rkward/windows/rkoutputwindow.rc
+++ b/rkward/windows/rkoutputwindow.rc
@@ -28,7 +28,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		<Menu name="view"><text>&amp;View</text>
 			<Action name="zoom_in"/>
 			<Action name="zoom_out"/>
-			<Action name="view_encoding"/>
 			<Separator/>
 			<Action name="output_refresh"/>
 		</Menu>
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
new file mode 100644
index 000000000..29c2795a8
--- /dev/null
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -0,0 +1,379 @@
+/*
+rkqwebenginewidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#include "rkqwebenginewidget.h"
+
+#include <QBuffer>
+#include <QDir>
+#include <QFileDialog>
+#include <QFontDatabase>
+#include <QMimeDatabase>
+#include <QPrintDialog>
+#include <QWebEngineFindTextResult>
+#include <QWebEnginePage>
+#include <QWebEngineProfile>
+#include <QWebEngineScript>
+#include <QWebEngineScriptCollection>
+#include <QWebEngineSettings>
+#include <QWebEngineUrlRequestJob>
+#include <QWebEngineUrlSchemeHandler>
+#include <QWebEngineView>
+#include <QWheelEvent>
+
+#include <KIO/StoredTransferJob>
+#include <KLocalizedString>
+
+#include "../misc/rkfindbar.h"
+#include "../rkward.h"
+#include "../settings/rksettingsmoduler.h"
+#include "rkhtmlwindow.h"
+#include "rkworkplace.h"
+
+#include "../debug.h"
+
+// TODO: remove dupe
+static QUrl restorableUrl(const QUrl &url) {
+	return QUrl(url.url().replace(RKSettingsModuleR::helpBaseUrl(), QLatin1String("rkward://RHELPBASE")));
+}
+
+class RKWebPage : public QWebEnginePage {
+	Q_OBJECT
+  public:
+	explicit RKWebPage(RKHTMLWindow *window) : QWebEnginePage(window) {
+		RK_TRACE(APP);
+		RKWebPage::window = window;
+		direct_load = false;
+		settings()->setFontFamily(QWebEngineSettings::StandardFont, QFontDatabase::systemFont(QFontDatabase::GeneralFont).family());
+		settings()->setFontFamily(QWebEngineSettings::FixedFont, QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
+	}
+
+	void load(const QUrl &url) {
+		RK_TRACE(APP);
+		direct_load = true;
+		QWebEnginePage::load(url);
+	}
+
+	void setHtmlWrapper(const QString &html, const QUrl &baseurl) {
+		direct_load = true;
+		setHtml(html, baseurl);
+	}
+	bool supportsContentType(const QString &name) {
+		if (name.startsWith(QLatin1String("text"))) return true;
+		return false;
+	}
+	void downloadUrl(const QUrl &url) {
+		download(url);
+	}
+	void setScrollPosition(const QPoint &point) {
+		RK_DEBUG(APP, DL_DEBUG, "scrolling to %d, %d", point.x(), point.y());
+		runJavaScript(QStringLiteral("window.scrollTo(%1, %2);").arg(point.x()).arg(point.y()));
+	}
+	void setScrollPositionWhenDone(const QPoint &pos) {
+		QMetaObject::Connection *const connection = new QMetaObject::Connection;
+		*connection = connect(this, &QWebEnginePage::loadFinished, [this, pos, connection]() {
+			QObject::disconnect(*connection);
+			delete connection;
+			setScrollPosition(pos);
+		});
+	}
+
+  Q_SIGNALS:
+	void pageInternalNavigation(const QUrl &url);
+
+  protected:
+	bool acceptNavigationRequest(const QUrl &navurl, QWebEnginePage::NavigationType type, bool is_main_frame) override {
+		QUrl cururl(url());
+		Q_UNUSED(type);
+
+		RK_TRACE(APP);
+		RK_DEBUG(APP, DL_DEBUG, "Navigation request to %s", qPrintable(navurl.toString()));
+		if (direct_load && (is_main_frame)) {
+			direct_load = false;
+			return true;
+		}
+
+		if (opened_as_new) {
+			RK_ASSERT(opened_as_new == this);
+			RK_ASSERT(!window);
+			RKWorkplace::mainWorkplace()->openAnyUrl(restorableUrl(navurl));
+			opened_as_new = nullptr;
+			if (!window) deleteLater(); // this page was _not_ reused
+			return false;
+		}
+		RK_ASSERT(window);
+
+		if (!is_main_frame) {
+			if (navurl.isLocalFile() && supportsContentType(QMimeDatabase().mimeTypeForUrl(navurl).name())) return true;
+		}
+
+		if (cururl.matches(navurl, QUrl::NormalizePathSegments | QUrl::StripTrailingSlash)) {
+			RK_DEBUG(APP, DL_DEBUG, "Page internal navigation request from %s to %s", qPrintable(cururl.toString()), qPrintable(navurl.toString()));
+			Q_EMIT pageInternalNavigation(navurl);
+			return true;
+		}
+
+		window->openURL(navurl);
+		return false;
+	}
+
+	QWebEnginePage *createWindow(QWebEnginePage::WebWindowType) override {
+		RK_TRACE(APP);
+		RKWebPage *ret = new RKWebPage(nullptr);
+		opened_as_new = ret; // Don't actually create a full window, until we know which URL we're talking about.
+		// sigh: acceptNavigationRequest() does  not get called on the new page...
+		QMetaObject::Connection *const connection = new QMetaObject::Connection;
+		*connection = connect(ret, &RKWebPage::loadStarted, [ret, connection
+#ifdef _MSC_VER
+		                                                     ,
+		                                                     this // capturing "this" makes MSVC happy
+#endif
+		]() {
+			QObject::disconnect(*connection);
+			delete connection;
+			ret->acceptNavigationRequest(ret->url(), QWebEnginePage::NavigationTypeLinkClicked, true);
+		});
+		return (ret);
+	}
+
+	friend class RKQWebEngineWidget;
+	RKHTMLWindow *window;
+	bool direct_load;
+	static RKWebPage *opened_as_new;
+};
+RKWebPage *RKWebPage::opened_as_new = nullptr;
+
+class RKWebView : public QWebEngineView {
+  public:
+	explicit RKWebView(QWidget *parent) : QWebEngineView(parent) {};
+	void print(QPrinter *printer) {
+		if (!page()) return;
+		QWebEngineView::forPage(page())->print(printer);
+	};
+
+  protected:
+	bool eventFilter(QObject *, QEvent *event) override {
+		if (event->type() == QEvent::Wheel) {
+			QWheelEvent *we = static_cast<QWheelEvent *>(event);
+			if (we->modifiers() & Qt::ControlModifier) {
+				setZoomFactor(zoomFactor() + we->angleDelta().y() / 1200.0);
+				return true;
+			}
+		}
+		return false;
+	}
+	void childEvent(QChildEvent *event) override {
+		if (event->type() == QChildEvent::ChildAdded) {
+			event->child()->installEventFilter(this);
+		}
+	}
+	// NOTE: Code below won't work, due to https://bugreports.qt.io/browse/QTBUG-43602
+	/*	void wheelEvent (QWheelEvent *event) override {
+	        [handle zooming]
+	    } */
+};
+
+class RKWebEngineKIOForwarder : public QWebEngineUrlSchemeHandler {
+  public:
+	explicit RKWebEngineKIOForwarder(QObject *parent) : QWebEngineUrlSchemeHandler(parent) {}
+	void requestStarted(QWebEngineUrlRequestJob *request) override {
+		RK_DEBUG(APP, DL_DEBUG, "new KIO request to %s", qPrintable(request->requestUrl().url()));
+		KIO::StoredTransferJob *job = KIO::storedGet(request->requestUrl(), KIO::NoReload, KIO::HideProgressInfo);
+		connect(job, &KIO::StoredTransferJob::result, this, [this, job]() { kioJobFinished(job); });
+		jobs.insert(job, request);
+	}
+
+  private:
+	void kioJobFinished(KIO::StoredTransferJob *job) {
+		QWebEngineUrlRequestJob *request = jobs.take(job);
+		if (!request) {
+			return;
+		}
+		if (job->error()) {
+			request->fail(QWebEngineUrlRequestJob::UrlInvalid); // TODO
+			return;
+		}
+		QBuffer *buf = new QBuffer(request);
+		buf->setData(job->data());
+		request->reply(QMimeDatabase().mimeTypeForData(job->data()).name().toUtf8(), buf);
+	}
+	QMap<KIO::StoredTransferJob *, QPointer<QWebEngineUrlRequestJob>> jobs;
+};
+
+RKQWebEngineWidget::RKQWebEngineWidget(RKHTMLWindow *parent) : RKHTMLViewer(parent) {
+	RK_TRACE(APP);
+
+	if (RKWebPage::opened_as_new) {
+		page = RKWebPage::opened_as_new;
+		RKWebPage::opened_as_new = nullptr;
+		RKWebPage::opened_as_new->window = parent;
+	} else {
+		page = new RKWebPage(parent);
+	}
+}
+
+QWidget *RKQWebEngineWidget::createWidget(QWidget *parent) {
+	RK_TRACE(APP);
+
+	// We keep our own history.
+	page->action(RKWebPage::Back)->setVisible(false);
+	page->action(RKWebPage::Forward)->setVisible(false);
+	// For now we won't bother with this one: Does not behave well, in particular (but not only) WRT to rkward://-links
+	page->action(RKWebPage::DownloadLinkToDisk)->setVisible(false);
+	// Not really useful for us, and cannot easily be made to work, as all new pages go through RKWorkplace::openAnyUrl()
+	page->action(RKWebPage::ViewSource)->setVisible(false);
+	// Well, technically, all our windows are tabs, but we're calling them "window".
+	// At any rate, we don't need both "open link in new tab" and "open link in new window".
+	page->action(RKWebPage::OpenLinkInNewTab)->setVisible(false);
+
+	connect(page, &RKWebPage::printRequested, this, &RKQWebEngineWidget::print);
+	connect(page->profile(), &QWebEngineProfile::downloadRequested, this, [this](QWebEngineDownloadRequest *item) {
+		QString defpath;
+		defpath = QDir(item->downloadDirectory()).absoluteFilePath(item->downloadFileName());
+		QString path = QFileDialog::getSaveFileName(window, i18n("Save as"), defpath);
+		if (path.isEmpty()) return;
+		QFileInfo fi(path);
+		item->setDownloadDirectory(fi.absolutePath());
+		item->setDownloadFileName(fi.fileName());
+		item->accept();
+	});
+	connect(page, &RKWebPage::loadFinished, this, [this]() {
+		Q_EMIT loadFinished();
+	});
+
+	RK_ASSERT(!view);
+	view = new QWebEngineView(page, parent);
+	connect(view, &QWebEngineView::selectionChanged, this, [this]() {
+		Q_EMIT selectionChanged(view->hasSelection());
+	});
+	return view;
+}
+
+void RKQWebEngineWidget::reload() {
+	RK_TRACE(APP);
+	auto a = page->action(RKWebPage::Reload);
+	RK_ASSERT(a);
+	if (a) a->trigger();
+}
+
+QUrl RKQWebEngineWidget::url() const {
+	RK_TRACE(APP);
+	return page->url();
+}
+
+void RKQWebEngineWidget::load(const QUrl &url) {
+	RK_TRACE(APP);
+	// TODO: We seem to be loading start page multiple times?
+	RK_DEBUG(APP, DL_WARNING, qPrintable(url.url()));
+	page->load(url);
+}
+
+QString RKQWebEngineWidget::selectedText() const {
+	RK_TRACE(APP);
+	if (!view) return QString();
+	return view->selectedText();
+}
+
+void RKQWebEngineWidget::exportPage() {
+	RK_TRACE(APP);
+	page->downloadUrl(page->url());
+}
+
+QPoint RKQWebEngineWidget::scrollPosition() const {
+	RK_TRACE(APP);
+	return page->scrollPosition().toPoint();
+}
+
+void RKQWebEngineWidget::setScrollPosition(const QPoint &pos, bool wait_for_load) {
+	RK_TRACE(APP);
+	if (wait_for_load) {
+		page->setScrollPositionWhenDone(pos);
+	} else {
+		page->setScrollPosition(pos);
+	}
+}
+
+void RKQWebEngineWidget::zoomIn() {
+	RK_TRACE(APP);
+	if (view) view->setZoomFactor(view->zoomFactor() * 1.1);
+}
+
+void RKQWebEngineWidget::zoomOut() {
+	RK_TRACE(APP);
+	if (view) view->setZoomFactor(view->zoomFactor() / 1.1);
+}
+
+bool RKQWebEngineWidget::supportsContentType(const QString &mimename) {
+	RK_TRACE(APP);
+	return page->supportsContentType(mimename);
+}
+
+void RKQWebEngineWidget::print() {
+	RK_TRACE(APP);
+	// NOTE: taken from kwebkitpart, with small mods
+	// Make it non-modal, in case a redirection deletes the part
+	QPointer<QPrintDialog> dlg(new QPrintDialog(view));
+	if (dlg->exec() == QPrintDialog::Accepted) {
+		view->print(dlg->printer());
+	}
+	delete dlg;
+}
+
+void RKQWebEngineWidget::installPersistentJS(const QString &script, const QString &id) {
+	RK_TRACE(APP);
+
+	auto p = QWebEngineProfile::defaultProfile();
+	QWebEngineScript we_script;
+	const auto scripts = p->scripts()->find(id);
+	for (const auto &script : scripts) {
+		p->scripts()->remove(script); // remove any existing variant of the script. It might have been created for the wrong theme.
+	}
+	we_script.setName(id);
+	we_script.setInjectionPoint(QWebEngineScript::DocumentReady);
+	we_script.setSourceCode(script);
+	p->scripts()->insert(we_script);
+}
+
+void RKQWebEngineWidget::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
+	RK_TRACE(APP);
+	page->runJavaScript(script, callback);
+}
+
+void RKQWebEngineWidget::setHTML(const QString &html, const QUrl &url) {
+	RK_TRACE(APP);
+	page->setHtmlWrapper(html, url);
+}
+
+bool RKQWebEngineWidget::installHelpProtocolHandler() {
+	RK_TRACE(APP);
+
+	if (!QWebEngineProfile::defaultProfile()->urlSchemeHandler("help")) {
+		QWebEngineProfile::defaultProfile()->installUrlSchemeHandler("help", new RKWebEngineKIOForwarder(RKWardMainWindow::getMain()));
+	}
+	return true;
+}
+
+void RKQWebEngineWidget::findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) {
+	RK_TRACE(APP);
+
+	// QWebEngine does not offer highlight all
+	*found = true; // real result is not available, synchronously
+	QWebEnginePage::FindFlags flags;
+	if (backwards) flags |= QWebEnginePage::FindBackward;
+	if (findbar->isOptionSet(RKFindBar::MatchCase)) flags |= QWebEnginePage::FindCaseSensitively;
+	page->findText(text, flags, [this, findbar](QWebEngineFindTextResult result) {
+		if (result.numberOfMatches() == 0) {
+			findbar->indicateSearchFail();
+		}
+	});
+}
+
+QMenu *RKQWebEngineWidget::createContextMenu(const QPoint &clickpos) {
+	RK_TRACE(APP);
+	return (QWebEngineView::forPage(page)->createStandardContextMenu());
+}
+
+#include "rkqwebenginewidget.moc"
diff --git a/rkward/windows/rkqwebenginewidget.h b/rkward/windows/rkqwebenginewidget.h
new file mode 100644
index 000000000..297eba049
--- /dev/null
+++ b/rkward/windows/rkqwebenginewidget.h
@@ -0,0 +1,46 @@
+/*
+rkqwebenginewidget - This file is part of the RKWard project. Created: Sat Feb 07 2026
+SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+SPDX-License-Identifier: GPL-2.0-or-later
+*/
+
+#ifndef RKQWEBENGINEWIDGET_H
+#define RKQWEBENGINEWIDGET_H
+
+#include <QPointer>
+
+#include "rkhtmlviewer.h"
+
+class RKWebPage;
+class QWebEngineView;
+
+class RKQWebEngineWidget : public RKHTMLViewer {
+  public:
+	QWidget *createWidget(QWidget *parent) override;
+	void reload() override;
+	QUrl url() const override;
+	void load(const QUrl &url) override;
+	void print() override;
+	void installPersistentJS(const QString &script, const QString &id) override;
+	void runJS(const QString &script, std::function<void(const QVariant &)> callback) override;
+	void setHTML(const QString &html, const QUrl &url) override;
+	bool installHelpProtocolHandler() override;
+	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
+	QMenu *createContextMenu(const QPoint &clickpos) override;
+	QString selectedText() const override;
+	void exportPage() override;
+	QPoint scrollPosition() const override;
+	void setScrollPosition(const QPoint &pos, bool wait_for_load) override;
+	bool supportsContentType(const QString &mimename) override;
+	void zoomIn() override;
+	void zoomOut() override;
+
+  private:
+	friend class RKHTMLViewer;
+	RKQWebEngineWidget(RKHTMLWindow *parent);
+	RKWebPage *page;
+	QPointer<QWebEngineView> view;
+};
+
+#endif


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward=3A_Implement_more_functions_in_rkqwebview?=
Message-ID: <20260409194424.649A81401BBB@lerwini.kde.org>

Git commit 0e42417f5704dcb88a41e7c450db15cff88f891f by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Implement more functions in rkqwebview

M  +1    -1    rkward/main.cpp
M  +1    -1    rkward/windows/rkhtmlwindow.cpp
M  +61   -22   rkward/windows/rkqwebview.cpp
M  +10   -2    rkward/windows/rkqwebview.h
M  +17   -9    rkward/windows/rkqwebview.qml
M  +7    -1    rkward/windows/rkworkplace.cpp
M  +3    -0    rkward/windows/rkworkplace.h

https://invent.kde.org/education/rkward/-/commit/0e42417f5704dcb88a41e7c450db15cff88f891f

diff --git a/rkward/main.cpp b/rkward/main.cpp
index c69dbb753..61b494637 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -154,8 +154,8 @@ int main(int argc, char *argv[]) {
 	scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
 	QWebEngineUrlScheme::registerScheme(scheme);
 	BreezeIcons::initIcons(); // install as fallback theme. Too many issues with missing icons, otherwise
-	QtWebView::initialize();
 	QApplication app(argc, argv);
+	QtWebView::initialize();
 	KDSingleApplication app_singleton;
 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
 	// Follow the example of kate, and use breeze theme on Windows and Mac, which appears to work best
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index a6d5d3984..06f369228 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -89,7 +89,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	                              .arg(scheme->foreground().color().name(), scheme->background().color().name(),
 	                                   scheme->foreground(KColorScheme::VisitedText).color().name(), scheme->foreground(KColorScheme::LinkText).color().name(),
 	                                   scheme->shade(KColorScheme::MidShade).name());
-	page->installPersistentJS(u"fix_color_scheme"_s, color_scheme_js);
+	page->installPersistentJS(color_scheme_js, u"fix_color_scheme"_s);
 	page->installHelpProtocolHandler();
 
 	layout->addWidget(findbar);
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 1d40cd158..e6d7eeaa1 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -11,7 +11,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QQuickItem>
 #include <QQuickWidget>
 
+#include "../misc/rkfindbar.h"
 #include "rkhtmlwindow.h"
+#include "rkworkplace.h"
 
 #include "../debug.h"
 
@@ -19,38 +21,54 @@ RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullpt
 	RK_TRACE(APP);
 }
 
-void RKQWebView::onPageLoad(const QUrl &_url, const QString &error, int status) {
-	RK_DEBUG(APP, DL_WARNING, "loading %s %d", qPrintable(_url.toString()), status);
-	/* TODO: we may need to inject a script similar for handling target=new
-	    and page internal navigation?
-	RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
-	    "  const origin = e.target.closest('a');"
-	    "  if (origin) alert(origin.href);"
-	    "})\n"_s); */
-	if (status == 0) {
-		bool new_window = false;
-		Q_EMIT navigationRequest(webView()->property("acceptedUrl").toUrl(), _url, new_window);
-		// we may be redirected via the above signal
-		if (_url != url()) {
-			RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
-		}
-	} else if (status == 2) {
-		Q_EMIT loadFinished();
+QUrl RKQWebView::currentAcceptedUrl() const {
+	return webView()->property("acceptedUrl").toUrl();
+}
+
+void RKQWebView::onUrlChanged(const QUrl &_url, const QString &error, int status) {
+	RK_DEBUG(APP, DL_DEBUG, "webview url changed to %s status %d, msg %s", qPrintable(_url.toString()), status, qPrintable(error));
+	bool new_window = false;
+	Q_EMIT navigationRequest(currentAcceptedUrl(), _url, new_window);
+	// we may be redirected via the above signal
+	if (_url != url()) {
+		RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
+	}
+	if (status == 0 && _url.scheme() == u"rkward") {
+		// Some QWebView plugins will also trigger openRKWardURl() via QDesktopServices::setUrlHandler()
+		// Others won't. In either case, we've handled it from here.
+		RKWorkplace::mainWorkplace()->suppressRKWardUrlHandling(_url);
+	}
+}
+
+void RKQWebView::onLoadFinished(const QUrl &url) {
+	RK_TRACE(APP);
+	RK_ASSERT(currentAcceptedUrl() == url);
+	for (const auto &script : std::as_const(persistentScripts)) {
+		RKHTMLViewer::runJS(script);
 	}
+	Q_EMIT loadFinished();
 }
 
 QWidget *RKQWebView::createWidget() {
 	RK_TRACE(APP);
 	view = new QQuickWidget();
-	view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
 	view->setResizeMode(QQuickWidget::SizeRootObjectToView);
-	connect(webView(), SIGNAL(loaded(const QUrl &, const QString &, int)), this, SLOT(onPageLoad(const QUrl &, const QString &, int)));
+	view->setSource(QUrl(QStringLiteral("qrc:/qml/rkqwebview.qml")));
+	connect(webView(), SIGNAL(pageUrlChanged(const QUrl &, const QString &, int)), this, SLOT(onUrlChanged(const QUrl &, const QString &, int)));
+	connect(webView(), SIGNAL(loadFinished(const QUrl &)), this, SLOT(onLoadFinished(const QUrl &)));
+	connect(webView(), SIGNAL(runJSResult(const QVariant &)), this, SLOT(onRunJSResult(const QVariant &)));
+	/* TODO: we may need to inject a script similar for handling target=new
+	    and page internal navigation?
+	RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
+	    "  const origin = e.target.closest('a');"
+	    "  if (origin) alert(origin.href);"
+	    "})\n"_s); */
 	return view;
 }
 
 QObject *RKQWebView::webView() const {
-	// TODO: we have some premature calls to this function. Find and fix them
 	if (!view) {
+		// could happen (happened in the past) due to premature calls to this function.
 		RK_ASSERT(view);
 		return nullptr;
 	}
@@ -77,20 +95,31 @@ void RKQWebView::load(const QUrl &url) {
 
 void RKQWebView::print() {
 	RK_TRACE(APP);
+	// TODO: Doesn't work. Why?
 	RKHTMLViewer::runJS(u"window.print()"_s);
 }
 
 void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
 	RK_TRACE(APP);
+	persistentScripts[id] = script;
 }
 
 void RKQWebView::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
 	RK_TRACE(APP);
-	QMetaObject::invokeMethod(webView(), "runJavaScript", Q_ARG(QString, script), Q_ARG(QJSValue, QJSValue()));
+	RK_ASSERT(runjs_callback == nullptr); // no nested calls, please!
+	runjs_callback = callback;
+	QMetaObject::invokeMethod(webView(), "runJSWrapper", Q_ARG(QString, script));
+}
+
+void RKQWebView::onRunJSResult(const QVariant &result) {
+	RK_TRACE(APP);
+	if (runjs_callback != nullptr) runjs_callback(result);
+	runjs_callback = std::function<void(const QVariant &)>();
 }
 
-void RKQWebView::setHTML(const QString &html, const QUrl &url) {
+void RKQWebView::setHTML(const QString &html, const QUrl &_url) {
 	RK_TRACE(APP);
+	QMetaObject::invokeMethod(webView(), "loadHtml", Q_ARG(QString, html), Q_ARG(QUrl, _url));
 }
 
 bool RKQWebView::installHelpProtocolHandler() {
@@ -100,6 +129,16 @@ bool RKQWebView::installHelpProtocolHandler() {
 
 void RKQWebView::findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) {
 	RK_TRACE(APP);
+
+	*found = true; // real result is not available, synchronously
+	auto _txt = text;
+	// NOTE: window.find() is marked as not standardized at the time of this writing!
+	QString script = u"window.find('%1', %2, %3, true)"_s.arg(_txt.replace(u"'"_s, u"\\'"_s),
+	                                                          (findbar->isOptionSet(RKFindBar::MatchCase) ? u"true"_s : u"false"_s),
+	                                                          (backwards ? u"true"_s : u"false"_s));
+	runJS(script, [findbar](const QVariant &res) {
+		if (!res.toBool()) findbar->indicateSearchFail();
+	});
 }
 
 QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index 48511ed09..f681105c5 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -8,6 +8,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #ifndef RKQWEBVIEW_H
 #define RKQWEBVIEW_H
 
+#include <QMap>
 #include <QPointer>
 
 #include "rkhtmlviewer.h"
@@ -15,6 +16,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
 class QQuickWidget;
 class QQuickItem;
 
+/** Interface abstraction for QWebView. Note that QWebView is essentially QML only until Qt 6.10. Once
+ *  we can assume Qt 6.11,some simplifications will be possible. */
 class RKQWebView : public RKHTMLViewer {
 	Q_OBJECT
   public:
@@ -23,7 +26,7 @@ class RKQWebView : public RKHTMLViewer {
 	void load(const QUrl &url) override;
 	void print() override;
 	void installPersistentJS(const QString &script, const QString &id) override;
-	void runJS(const QString &script, std::function<void(const QVariant &)> callback) override;
+	void runJS(const QString &script, std::function<void(const QVariant &)> callback = std::function<void(const QVariant &)>()) override;
 	void setHTML(const QString &html, const QUrl &url) override;
 	bool installHelpProtocolHandler() override;
 	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
@@ -40,8 +43,13 @@ class RKQWebView : public RKHTMLViewer {
 	friend class RKHTMLViewer;
 	RKQWebView(RKHTMLWindow *parent);
 	QPointer<QQuickWidget> view;
+	QUrl currentAcceptedUrl() const;
+	QMap<QString, QString> persistentScripts;
+	std::function<void(const QVariant &)> runjs_callback;
   private Q_SLOTS:
-	void onPageLoad(const QUrl &url, const QString &error, int status);
+	void onUrlChanged(const QUrl &url, const QString &error, int status);
+	void onLoadFinished(const QUrl &url);
+	void onRunJSResult(const QVariant &result);
 	QObject *webView() const;
 };
 
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
index 1059a24ab..1efc367a2 100644
--- a/rkward/windows/rkqwebview.qml
+++ b/rkward/windows/rkqwebview.qml
@@ -13,7 +13,13 @@ Item {
         visible: true
 
         WebView {
-                signal loaded(url murl, string error, int status)
+                signal pageUrlChanged(url murl, string error, int status)
+                signal loadFinished(url murl)
+                signal runJSResult(variant result)
+
+                function runJSWrapper(script: string) {
+                        runJavaScript(script, runJSResult)
+                }
                 id: webView
                 objectName: "webView"
                 visible: true
@@ -22,14 +28,16 @@ Item {
                 property string acceptedUrl: ""
 
                 onLoadingChanged: request => {
-                        // give the frontend a chance to intervene, e.g. rewriting rkward:// url, opening in new window, 
-                        // denying external urls, etc.
-                        loaded(request.url, request.error, request.status);
-                        if (request.status == WebView.LoadStartedStatus) {
-                                if (request.url != acceptedUrl) {
-                                        //console.log("Navigation request to " + request.url + "denied");
-                                        url = acceptedUrl;
-                                }
+                        // notify frontend of any url changes, giving it a chance to intervene (e.g. rewriting
+                        // rkward:// urls etc.)
+                        // Note that the semantics of just when loadingChanged is emitted differ between the QWebView
+                        // plugins. E.g. for webengine, we get a LoadStartedStatus, then LoadFailedStatus for rkward://-urls,
+                        // while for webview2, we only get LoadFailedStatus.
+                        if (request.url != acceptedUrl) {
+                                pageUrlChanged(request.url, request.error, request.status);
+                        }
+                        if (request.status == WebView.LoadSucceededStatus) {
+                                loadFinished(request.url);
                         }
                 }
         }
diff --git a/rkward/windows/rkworkplace.cpp b/rkward/windows/rkworkplace.cpp
index 369cd9f0b..ba7ed1de6 100644
--- a/rkward/windows/rkworkplace.cpp
+++ b/rkward/windows/rkworkplace.cpp
@@ -403,7 +403,13 @@ void RKWorkplace::namedWindowOwnerDestroyed(QObject *owner) {
 void RKWorkplace::openRKWardUrl(const QUrl &url) {
 	RK_TRACE(APP);
 	RK_ASSERT(url.scheme() == QLatin1String("rkward"));
-	RKHTMLWindow::handleRKWardURL(url);
+	if (url != suppressed_rkward_url) RKHTMLWindow::handleRKWardURL(url);
+	suppressed_rkward_url.clear();
+}
+
+void RKWorkplace::suppressRKWardUrlHandling(const QUrl &url) {
+	RK_TRACE(APP);
+	suppressed_rkward_url = url;
 }
 
 bool RKWorkplace::openAnyUrl(const QUrl &url, const QString &known_mimetype, bool force_external) {
diff --git a/rkward/windows/rkworkplace.h b/rkward/windows/rkworkplace.h
index 8cffaa8dd..6884fc3d5 100644
--- a/rkward/windows/rkworkplace.h
+++ b/rkward/windows/rkworkplace.h
@@ -229,6 +229,8 @@ class RKWorkplace : public QWidget {
 	/** Inform the workplace that this window is handled outside the regular attached/detached mechanisms (such as preview windows). Internally, this just sets the window to detached, without giving it a DetachedWindowContainer.
 	This seems good enough for now, but may be something to revisit in case of unexpected problems. */
 	void setWindowNotManaged(RKMDIWindow *window);
+	/** Used from RKQWebView */
+	void suppressRKWardUrlHandling(const QUrl &url);
   Q_SIGNALS:
 	/** emitted when the workspace Url has changed */
 	void workspaceUrlChanged(const QUrl &url);
@@ -284,6 +286,7 @@ class RKWorkplace : public QWidget {
 	RKMDIWindow::State window_placement_override;
 	QString window_name_override;
 	QString window_style_override;
+	QUrl suppressed_rkward_url;
 };
 
 #endif


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Do_not_open_help_pages_twice?=
Message-ID: <20260409194424.656601401BD2@lerwini.kde.org>

Git commit 01f395770ff78011fb2415c39983d95bfdb4613c by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Do not open help pages twice

M  +5    -3    rkward/windows/rkqwebview.cpp
M  +6    -0    rkward/windows/rkqwebview.qml

https://invent.kde.org/education/rkward/-/commit/01f395770ff78011fb2415c39983d95bfdb4613c

diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index e6d7eeaa1..208196fc1 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -26,14 +26,16 @@ QUrl RKQWebView::currentAcceptedUrl() const {
 }
 
 void RKQWebView::onUrlChanged(const QUrl &_url, const QString &error, int status) {
-	RK_DEBUG(APP, DL_DEBUG, "webview url changed to %s status %d, msg %s", qPrintable(_url.toString()), status, qPrintable(error));
+	// WebView status enum: 0: LoadStarted, 2: LoadFinished, 3: Error.
+	// I do not actually know, what status==1 is, but it does get sent in some cases. I assume it's "aborted"
+	RK_DEBUG(APP, DL_WARNING, "webview %p url changed to %s status %d, msg %s", this, qPrintable(_url.toString()), status, qPrintable(error));
 	bool new_window = false;
 	Q_EMIT navigationRequest(currentAcceptedUrl(), _url, new_window);
 	// we may be redirected via the above signal
 	if (_url != url()) {
 		RK_DEBUG(APP, DL_DEBUG, "redirecting %s to %s", qPrintable(_url.toString()), qPrintable(url().toString()));
 	}
-	if (status == 0 && _url.scheme() == u"rkward") {
+	if (status == 0 && _url.scheme() == u"rkward"_s) {
 		// Some QWebView plugins will also trigger openRKWardURl() via QDesktopServices::setUrlHandler()
 		// Others won't. In either case, we've handled it from here.
 		RKWorkplace::mainWorkplace()->suppressRKWardUrlHandling(_url);
@@ -57,7 +59,7 @@ QWidget *RKQWebView::createWidget() {
 	connect(webView(), SIGNAL(pageUrlChanged(const QUrl &, const QString &, int)), this, SLOT(onUrlChanged(const QUrl &, const QString &, int)));
 	connect(webView(), SIGNAL(loadFinished(const QUrl &)), this, SLOT(onLoadFinished(const QUrl &)));
 	connect(webView(), SIGNAL(runJSResult(const QVariant &)), this, SLOT(onRunJSResult(const QVariant &)));
-	/* TODO: we may need to inject a script similar for handling target=new
+	/* TODO: we may need to inject a script along the lines below for handling target=new
 	    and page internal navigation?
 	RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
 	    "  const origin = e.target.closest('a');"
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
index 1efc367a2..ca84b899d 100644
--- a/rkward/windows/rkqwebview.qml
+++ b/rkward/windows/rkqwebview.qml
@@ -26,6 +26,7 @@ Item {
                 width: parent.width
                 height: parent.height
                 property string acceptedUrl: ""
+                property string requestedUrl: ""
 
                 onLoadingChanged: request => {
                         // notify frontend of any url changes, giving it a chance to intervene (e.g. rewriting
@@ -35,6 +36,11 @@ Item {
                         // while for webview2, we only get LoadFailedStatus.
                         if (request.url != acceptedUrl) {
                                 pageUrlChanged(request.url, request.error, request.status);
+                                // if request did not get accepted: stay
+                                if (requestedUrl != acceptedUrl) {
+                                        // TODO: save and restore scroll position
+                                        url = acceptedUrl;
+                                }
                         }
                         if (request.status == WebView.LoadSucceededStatus) {
                                 loadFinished(request.url);


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Implement_scrolling?=
Message-ID: <20260409194424.6703A1401C1B@lerwini.kde.org>

Git commit 2c09bb4d760e6f667f8f65a6099f54a1720a0f0d by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Implement scrolling

M  +10   -7    rkward/windows/rkqwebview.cpp
M  +2    -0    rkward/windows/rkqwebview.h
M  +10   -0    rkward/windows/rkqwebview.js

https://invent.kde.org/education/rkward/-/commit/2c09bb4d760e6f667f8f65a6099f54a1720a0f0d

diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 0ac24e0a4..4e046b75f 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -112,12 +112,6 @@ QWidget *RKQWebView::createWidget() {
 	connect(webView(), SIGNAL(pageUrlChanged(const QUrl &, const QString &, int)), this, SLOT(onUrlChanged(const QUrl &, const QString &, int)));
 	connect(webView(), SIGNAL(loadFinished(const QUrl &)), this, SLOT(onLoadFinished(const QUrl &)));
 	connect(webView(), SIGNAL(runJSResult(const QVariant &)), this, SLOT(onRunJSResult(const QVariant &)));
-	/* TODO: we may need to inject a script along the lines below for handling target=new
-	    and page internal navigation?
-	RKHTMLViewer::runJS(u"document.addEventListener('click', e => {"
-	    "  const origin = e.target.closest('a');"
-	    "  if (origin) alert(origin.href);"
-	    "})\n"_s); */
 	return view;
 }
 
@@ -208,6 +202,7 @@ void RKQWebView::findRequest(const QString &text, bool backwards, RKFindBar *fin
 
 QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
 	RK_TRACE(APP);
+	// TODO
 	return new QMenu();
 }
 
@@ -228,6 +223,10 @@ void RKQWebView::receivedCallbackMessage(const QString &message) {
 			selected_text = sel;
 			Q_EMIT selectionChanged(!sel.isEmpty());
 		}
+	} else if (msg == "scroll"_L1) {
+		scroll_pos = QPoint(args["x"_L1].toInt(), args["y"_L1].toInt());
+	} else if (msg == "pageInternalNav"_L1) {
+		Q_EMIT pageInternalNavigation(QUrl(args["href"_L1].toString()));
 	} else {
 		RK_DEBUG(APP, DL_ERROR, "Non-recognized message %s", qPrintable(message));
 	}
@@ -239,22 +238,26 @@ void RKQWebView::exportPage() {
 
 QPoint RKQWebView::scrollPosition() const {
 	RK_TRACE(APP);
-	return QPoint();
+	return scroll_pos;
 }
 
 void RKQWebView::setScrollPosition(const QPoint &pos, bool wait_for_load) {
 	RK_TRACE(APP);
+	runJS(u"window.scroll({top: %1, left: %2, behavior: 'instant'});"_s.arg(pos.y(), pos.x()));
 }
 
 bool RKQWebView::supportsContentType(const QString &mimename) {
 	RK_TRACE(APP);
+	// TODO
 	return true;
 }
 
 void RKQWebView::zoomIn() {
 	RK_TRACE(APP);
+	// TODO
 }
 
 void RKQWebView::zoomOut() {
 	RK_TRACE(APP);
+	// TODO
 }
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index c6b73ada5..d37a8c126 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -9,6 +9,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #define RKQWEBVIEW_H
 
 #include <QMap>
+#include <QPoint>
 #include <QPointer>
 
 #include "rkhtmlviewer.h"
@@ -55,6 +56,7 @@ class RKQWebView : public RKHTMLViewer {
 	void tryRunNextScript();
 	void receivedCallbackMessage(const QString &message);
 	QString selected_text;
+	QPoint scroll_pos;
   private Q_SLOTS:
 	void onUrlChanged(const QUrl &url, const QString &error, int status);
 	void onLoadFinished(const QUrl &url);
diff --git a/rkward/windows/rkqwebview.js b/rkward/windows/rkqwebview.js
index 3c9473e6f..79b465563 100644
--- a/rkward/windows/rkqwebview.js
+++ b/rkward/windows/rkqwebview.js
@@ -15,6 +15,10 @@ document.onselectionchange = __rkward_debounce(function() {
 	__rkward_sendMessage("selChanged", { sel: document.getSelection().toString() });
 });
 
+document.onscroll = __rkward_debounce(function() {
+	__rkward_sendMessage("scroll", { x: window.scrollX, y: window.scrollY });
+});
+
 function __rkward_sendMessage(msg, args) {
 	if (__rkward.readyState == WebSocket.CONNECTING) {
 		setTimeout(__rkward_sendMessage.bind(null, msg, args), 10);
@@ -22,3 +26,9 @@ function __rkward_sendMessage(msg, args) {
 		__rkward.send(JSON.stringify({msg, args}));
 	}
 }
+
+document.addEventListener('click', e => {
+	const origin = e.target.closest('a');
+	if (origin && origin.href.startswith('#')) {
+		__rkward_sendMessage("pageInternalNav", { url: origin.href });
+});


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Handle_connection_lifetime?=
Message-ID: <20260409194424.67B071401C23@lerwini.kde.org>

Git commit f52b95149dc5599251fabb5c59e8386dfa77a92d by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Handle connection lifetime

M  +13   -8    rkward/windows/rkqwebview.cpp

https://invent.kde.org/education/rkward/-/commit/f52b95149dc5599251fabb5c59e8386dfa77a92d

diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 4e046b75f..791973dd9 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -30,13 +30,16 @@ class RKQWebViewCallbackServer {
 			QObject::connect(server, &QWebSocketServer::newConnection, server, [server]() {
 				auto con = server->nextPendingConnection();
 				const auto url = con->requestUrl().url();
-				auto win = windows.value(url);
-				RK_DEBUG(APP, DL_WARNING, "New connection at %s from window %p", qPrintable(url), win); // TODO
+				auto win = entrypoints.value(url);
+				entrypoints.remove(url);
+				RK_DEBUG(APP, DL_DEBUG, "New connection at %s from window %p", qPrintable(url), win);
 				if (!win) {
 					con->abort();
 				}
 
-				// TODO handle connection lifetime
+				QObject::connect(con, &QWebSocket::disconnected, win, [con, win]() {
+					con->deleteLater();
+				});
 
 				QObject::connect(con, &QWebSocket::textMessageReceived, win, [win](const QString &message) {
 					win->receivedCallbackMessage(message);
@@ -45,12 +48,12 @@ class RKQWebViewCallbackServer {
 		}
 
 		// Window may already be in our map. We need to re-init on every url change
-		auto key = windows.key(window);
+		auto key = entrypoints.key(window);
 		if (key.isEmpty()) {
 			key = server->serverUrl().url() + u"/"_s + QUuid::createUuid().toString(QUuid::Id128);
-			windows.insert(key, window);
+			entrypoints.insert(key, window);
 			QObject::connect(window, &QObject::destroyed, server, [key]() {
-				windows.remove(key);
+				entrypoints.remove(key);
 			});
 		}
 
@@ -58,9 +61,9 @@ class RKQWebViewCallbackServer {
 	}
 
   protected:
-	static QHash<QString, RKQWebView *> windows;
+	static QHash<QString, RKQWebView *> entrypoints;
 };
-QHash<QString, RKQWebView *> RKQWebViewCallbackServer::windows;
+QHash<QString, RKQWebView *> RKQWebViewCallbackServer::entrypoints;
 
 RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr), running_script(false) {
 	RK_TRACE(APP);
@@ -183,6 +186,7 @@ void RKQWebView::setHTML(const QString &html, const QUrl &_url) {
 
 bool RKQWebView::installHelpProtocolHandler() {
 	RK_TRACE(APP);
+	// TODO
 	return false;
 }
 
@@ -234,6 +238,7 @@ void RKQWebView::receivedCallbackMessage(const QString &message) {
 
 void RKQWebView::exportPage() {
 	RK_TRACE(APP);
+	// TODO
 }
 
 QPoint RKQWebView::scrollPosition() const {


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Silence_warnings?=
Message-ID: <20260409194424.6A74F1401D8F@lerwini.kde.org>

Git commit 2f5c4a8f26a1b04e889b8e4e5f890ad89cf45fe8 by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Silence warnings

M  +1    -1    rkward/windows/rkhtmlviewer.cpp
M  +1    -0    rkward/windows/rkhtmlwindow.cpp
M  +1    -1    rkward/windows/rkqwebenginewidget.cpp

https://invent.kde.org/education/rkward/-/commit/2f5c4a8f26a1b04e889b8e4e5f890ad89cf45fe8

diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
index 766e9e071..6b8c66dd8 100644
--- a/rkward/windows/rkhtmlviewer.cpp
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -20,7 +20,7 @@ RKHTMLViewer::RKHTMLViewer(RKHTMLWindow *parent) : QObject(parent), window(paren
 
 RKHTMLViewer *RKHTMLViewer::getNew(RKHTMLWindow *parent) {
 	RK_TRACE(APP);
-	const auto engine = RKSettingsModuleGeneral::htmlEngine();
+	const auto engine = RKSettingsModuleGeneral::htmlEngine(); // cppcheck-suppress unreadVariable
 #if RK_WITH_QWEBENGINE
 	if (engine == RKSettingsModuleGeneral::QWebEngineRenderingEngine) {
 		return new RKQWebEngineWidget(parent);
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 21c35a105..f33006ae1 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -105,6 +105,7 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	// We have to connect this in order to allow browsing.
 	connect(page, &RKHTMLViewer::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
 	connect(page, &RKHTMLViewer::navigationRequest, this, [this](const QUrl &current_real_url, const QUrl &requested_url, bool is_new_window) {
+		Q_UNUSED(current_real_url);
 		if (is_new_window) {
 			RKWorkplace::mainWorkplace()->openAnyUrl(requested_url);
 		} else {
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
index 6f71813f2..45a85dbcf 100644
--- a/rkward/windows/rkqwebenginewidget.cpp
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -375,7 +375,7 @@ void RKQWebEngineWidget::findRequest(const QString &text, bool backwards, RKFind
 	QWebEnginePage::FindFlags flags;
 	if (backwards) flags |= QWebEnginePage::FindBackward;
 	if (findbar->isOptionSet(RKFindBar::MatchCase)) flags |= QWebEnginePage::FindCaseSensitively;
-	page->findText(text, flags, [this, findbar](QWebEngineFindTextResult result) {
+	page->findText(text, flags, [findbar](QWebEngineFindTextResult result) {
 		if (result.numberOfMatches() == 0) {
 			findbar->indicateSearchFail();
 		}

From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/windows=3A_Add_basic_context_menu_to_qwebview=2C_implement_zoom?=
Message-ID: <20260409194424.689A91401C33@lerwini.kde.org>

Git commit b645612c82054ef00ecfd93fdc4ccd9e6a2aa9a5 by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Add basic context menu to qwebview, implement zoom

M  +2    -1    rkward/windows/rkhtmlviewer.cpp
M  +2    -2    rkward/windows/rkhtmlviewer.h
M  +14   -21   rkward/windows/rkhtmlwindow.cpp
M  +1    -1    rkward/windows/rkhtmlwindow.h
M  +10   -5    rkward/windows/rkqwebenginewidget.cpp
M  +0    -1    rkward/windows/rkqwebenginewidget.h
M  +62   -12   rkward/windows/rkqwebview.cpp
M  +2    -1    rkward/windows/rkqwebview.h
M  +14   -0    rkward/windows/rkqwebview.js
M  +6    -0    rkward/windows/rkqwebview.qml

https://invent.kde.org/education/rkward/-/commit/b645612c82054ef00ecfd93fdc4ccd9e6a2aa9a5

diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
index 47c356847..0176a625e 100644
--- a/rkward/windows/rkhtmlviewer.cpp
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -7,12 +7,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkhtmlviewer.h"
 
+#include "rkhtmlwindow.h"
 #include "rkqwebenginewidget.h"
 #include "rkqwebview.h"
 
 #include "../debug.h"
 
-RKHTMLViewer::RKHTMLViewer(QObject *parent) : QObject(parent) {
+RKHTMLViewer::RKHTMLViewer(RKHTMLWindow *parent) : QObject(parent), window(parent) {
 	RK_TRACE(APP);
 }
 
diff --git a/rkward/windows/rkhtmlviewer.h b/rkward/windows/rkhtmlviewer.h
index 00fdc4c23..d3b578c7e 100644
--- a/rkward/windows/rkhtmlviewer.h
+++ b/rkward/windows/rkhtmlviewer.h
@@ -43,7 +43,6 @@ class RKHTMLViewer : public QObject {
 	virtual void setScrollPosition(const QPoint &pos, bool wait_for_load) = 0;
 	// TODO: Could be implemented in base-class as JS call
 	virtual void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) = 0;
-	virtual QMenu *createContextMenu(const QPoint &clickpos) = 0;
 	virtual QString selectedText() const = 0;
 	virtual bool supportsContentType(const QString &mimename) = 0;
 	virtual void zoomIn() = 0;
@@ -54,9 +53,10 @@ class RKHTMLViewer : public QObject {
 	void selectionChanged(bool has_selection);
 	void loadFinished();
 	void navigationRequest(const QUrl &current_real_url, const QUrl &requested_url, bool is_new_window);
+	void aboutToShowContextMenu(QMenu *menu);
 
   protected:
-	RKHTMLViewer(QObject *parent);
+	RKHTMLViewer(RKHTMLWindow *parent);
 	RKHTMLWindow *window;
 };
 
diff --git a/rkward/windows/rkhtmlwindow.cpp b/rkward/windows/rkhtmlwindow.cpp
index 06f369228..21c35a105 100644
--- a/rkward/windows/rkhtmlwindow.cpp
+++ b/rkward/windows/rkhtmlwindow.cpp
@@ -72,7 +72,6 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	layout->setContentsMargins(0, 0, 0, 0);
 	page = RKHTMLViewer::getNew(this);
 	auto view = page->createWidget();
-	view->setContextMenuPolicy(Qt::CustomContextMenu);
 	layout->addWidget(view, 1);
 	findbar = new RKFindBar(this, true);
 	findbar->setPrimaryOptions(QList<QWidget *>() << findbar->getOption(RKFindBar::FindAsYouType) << findbar->getOption(RKFindBar::MatchCase));
@@ -106,10 +105,15 @@ RKHTMLWindow::RKHTMLWindow(QWidget *parent, WindowMode mode) : RKMDIWindow(paren
 	// We have to connect this in order to allow browsing.
 	connect(page, &RKHTMLViewer::pageInternalNavigation, this, &RKHTMLWindow::internalNavigation);
 	connect(page, &RKHTMLViewer::navigationRequest, this, [this](const QUrl &current_real_url, const QUrl &requested_url, bool is_new_window) {
-		openURL(requested_url);
+		if (is_new_window) {
+			RKWorkplace::mainWorkplace()->openAnyUrl(requested_url);
+		} else {
+			openURL(requested_url);
+		}
+	});
+	connect(page, &RKHTMLViewer::aboutToShowContextMenu, this, [this](QMenu *menu) {
+		menu->addAction(part->run_selection);
 	});
-
-	connect(view, &QWidget::customContextMenuRequested, this, &RKHTMLWindow::makeContextMenu);
 
 	current_history_position = -1;
 	url_change_is_from_history = false;
@@ -133,15 +137,6 @@ QUrl RKHTMLWindow::restorableUrl() {
 	return ::restorableUrl(current_url);
 }
 
-void RKHTMLWindow::makeContextMenu(const QPoint &pos) {
-	RK_TRACE(APP);
-
-	auto menu = page->createContextMenu(pos);
-	menu->addAction(part->run_selection);
-	menu->exec(mapToGlobal(pos));
-	delete (menu);
-}
-
 void RKHTMLWindow::selectionChanged(bool have_selection) {
 	RK_TRACE(APP);
 
@@ -159,6 +154,11 @@ void RKHTMLWindow::runSelection() {
 	RKConsole::pipeUserCommand(page->selectedText());
 }
 
+void RKHTMLWindow::slotCopy() {
+	RK_TRACE(APP);
+	qApp->clipboard()->setText(page->selectedText());
+}
+
 void RKHTMLWindow::slotPrint() {
 	RK_TRACE(APP);
 	page->print();
@@ -648,14 +648,7 @@ void RKHTMLWindowPart::initActions() {
 	RK_TRACE(APP);
 
 	// common actions
-	actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window->page, [page = window->page]() {
-		page->runJS(u"{ Window.getSelection(); }"_s, [](const QVariant &res) {
-			const auto txt = res.toString();
-			if (!txt.isEmpty()) {
-				qApp->clipboard()->setText(txt);
-			}
-		});
-	});
+	actionCollection()->addAction(KStandardAction::Copy, QStringLiteral("copy"), window, &RKHTMLWindow::slotCopy);
 	QAction *zoom_in = actionCollection()->addAction(QStringLiteral("zoom_in"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-in")), i18n("Zoom In"), this));
 	connect(zoom_in, &QAction::triggered, window, &RKHTMLWindow::zoomIn);
 	QAction *zoom_out = actionCollection()->addAction(QStringLiteral("zoom_out"), new QAction(QIcon::fromTheme(QStringLiteral("zoom-out")), i18n("Zoom Out"), this));
diff --git a/rkward/windows/rkhtmlwindow.h b/rkward/windows/rkhtmlwindow.h
index 51f3edcd6..0452d7e4d 100644
--- a/rkward/windows/rkhtmlwindow.h
+++ b/rkward/windows/rkhtmlwindow.h
@@ -75,6 +75,7 @@ class RKHTMLWindow : public RKMDIWindow {
 
 	WindowMode mode() { return window_mode; };
   public Q_SLOTS:
+	void slotCopy();
 	void slotPrint();
 	void slotExport();
 	void slotSave();
@@ -98,7 +99,6 @@ class RKHTMLWindow : public RKMDIWindow {
 	void mimeTypeJobFail(KJob *);
 	void mimeTypeJobFail2(KJob *);
 	void internalNavigation(const QUrl &new_url);
-	void makeContextMenu(const QPoint &pos);
 
   private:
 	friend class RKHTMLWindowPart;
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
index e345f2915..d975129fc 100644
--- a/rkward/windows/rkqwebenginewidget.cpp
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -11,6 +11,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QDir>
 #include <QFileDialog>
 #include <QFontDatabase>
+#include <QMenu>
 #include <QMimeDatabase>
 #include <QPrintDialog>
 #include <QWebEngineFindTextResult>
@@ -249,6 +250,15 @@ QWidget *RKQWebEngineWidget::createWidget() {
 	connect(view, &QWebEngineView::selectionChanged, this, [this]() {
 		Q_EMIT selectionChanged(view->hasSelection());
 	});
+	view->setContextMenuPolicy(Qt::CustomContextMenu);
+	connect(view, &QWidget::customContextMenuRequested, this, [this](const QPoint &pos) {
+		auto v = QWebEngineView::forPage(page);
+		auto menu = v->createStandardContextMenu();
+		Q_EMIT aboutToShowContextMenu(menu);
+		menu->exec(v->mapToGlobal(pos));
+		delete menu;
+	});
+
 	return view;
 }
 
@@ -371,9 +381,4 @@ void RKQWebEngineWidget::findRequest(const QString &text, bool backwards, RKFind
 	});
 }
 
-QMenu *RKQWebEngineWidget::createContextMenu(const QPoint &clickpos) {
-	RK_TRACE(APP);
-	return (QWebEngineView::forPage(page)->createStandardContextMenu());
-}
-
 #include "rkqwebenginewidget.moc"
diff --git a/rkward/windows/rkqwebenginewidget.h b/rkward/windows/rkqwebenginewidget.h
index 205cc6308..998384bfe 100644
--- a/rkward/windows/rkqwebenginewidget.h
+++ b/rkward/windows/rkqwebenginewidget.h
@@ -27,7 +27,6 @@ class RKQWebEngineWidget : public RKHTMLViewer {
 	void setHTML(const QString &html, const QUrl &url) override;
 	bool installHelpProtocolHandler() override;
 	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
-	QMenu *createContextMenu(const QPoint &clickpos) override;
 	QString selectedText() const override;
 	void exportPage() override;
 	QPoint scrollPosition() const override;
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 791973dd9..78f0a67fa 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -7,6 +7,10 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkqwebview.h"
 
+#include <KLocalizedString>
+#include <KStandardActions>
+
+#include <QClipboard>
 #include <QFile>
 #include <QMenu>
 #include <QQuickItem>
@@ -23,11 +27,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 class RKQWebViewCallbackServer {
   public:
 	static void initConnection(RKQWebView *window) {
-		static QWebSocketServer *server = nullptr;
 		if (!server) {
 			server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode);
 			server->listen(QHostAddress::LocalHost);
-			QObject::connect(server, &QWebSocketServer::newConnection, server, [server]() {
+			QObject::connect(server, &QWebSocketServer::newConnection, server, []() {
+				auto server = RKQWebViewCallbackServer::server;
 				auto con = server->nextPendingConnection();
 				const auto url = con->requestUrl().url();
 				auto win = entrypoints.value(url);
@@ -62,10 +66,12 @@ class RKQWebViewCallbackServer {
 
   protected:
 	static QHash<QString, RKQWebView *> entrypoints;
+	static QWebSocketServer *server;
 };
 QHash<QString, RKQWebView *> RKQWebViewCallbackServer::entrypoints;
+QWebSocketServer *RKQWebViewCallbackServer::server = nullptr;
 
-RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr), running_script(false) {
+RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr), running_script(false), context_menu(nullptr) {
 	RK_TRACE(APP);
 	QFile file(u":/js/rkqwebview.js"_s);
 	if (file.open(QIODevice::ReadOnly)) {
@@ -75,6 +81,11 @@ RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullpt
 	}
 }
 
+RKQWebView::~RKQWebView() {
+	RK_TRACE(APP);
+	delete context_menu;
+}
+
 QUrl RKQWebView::currentAcceptedUrl() const {
 	return webView()->property("acceptedUrl").toUrl();
 }
@@ -147,8 +158,15 @@ void RKQWebView::load(const QUrl &url) {
 
 void RKQWebView::print() {
 	RK_TRACE(APP);
-	// TODO: Doesn't work. Why?
+	// TODO: Doesn't do anything (no error, either). Why?
 	RKHTMLViewer::runJS(u"window.print()"_s);
+
+	/* This is not really a useful alternative...
+	QPointer<QPrintDialog> dlg(new QPrintDialog(view));
+	if (dlg->exec() == QPrintDialog::Accepted) {
+	    view->render(dlg->printer(), QPoint(), QRegion(), QWidget::DrawChildren);
+	}
+	delete dlg; */
 }
 
 void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
@@ -204,12 +222,6 @@ void RKQWebView::findRequest(const QString &text, bool backwards, RKFindBar *fin
 	});
 }
 
-QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
-	RK_TRACE(APP);
-	// TODO
-	return new QMenu();
-}
-
 QString RKQWebView::selectedText() const {
 	RK_TRACE(APP);
 
@@ -229,6 +241,44 @@ void RKQWebView::receivedCallbackMessage(const QString &message) {
 		}
 	} else if (msg == "scroll"_L1) {
 		scroll_pos = QPoint(args["x"_L1].toInt(), args["y"_L1].toInt());
+	} else if (msg == "contextMenu"_L1) {
+		// TODO: some actions we probably want:
+		// back, forward, reload, copy, copy link address, open in new tab, open in external browser,
+		// save image as
+		// TODO most to all of these actions could/should probably be handled in the RKHTMLWindow?
+		if (!context_menu) {
+			context_menu = new QMenu;
+			auto a = new QAction(i18n("Open link in new tab"), this);
+			a->setObjectName("open_new");
+			connect(a, &QAction::triggered, this, [a, this]() {
+				Q_EMIT navigationRequest(url(), QUrl(a->property("_url").toString()), true);
+			});
+			context_menu->addAction(a);
+
+			// NOTE copy is already present as an action in the main window, but we want a separate
+			// one that we can show/hide
+			a = KStandardActions::copy(window, &RKHTMLWindow::slotCopy, this);
+			a->setObjectName("copy");
+			context_menu->addAction(a);
+
+			a = new QAction(i18n("Copy link address"), this);
+			connect(a, &QAction::triggered, this, [a]() {
+				qApp->clipboard()->setText(a->property("_url").toString());
+			});
+			a->setObjectName("copy_link");
+			context_menu->addAction(a);
+		}
+		const auto url = args["url"_L1].toString();
+		auto a = findChild<QAction *>("open_new", Qt::FindDirectChildrenOnly);
+		a->setVisible(!url.isEmpty());
+		a->setProperty("_url", url);
+		a = findChild<QAction *>("copy_link", Qt::FindDirectChildrenOnly);
+		a->setVisible(!url.isEmpty());
+		a->setProperty("_url", url);
+		a = findChild<QAction *>("copy", Qt::FindDirectChildrenOnly);
+		a->setVisible(!selected_text.isEmpty());
+		Q_EMIT aboutToShowContextMenu(context_menu);
+		context_menu->exec((QPoint(args["x"_L1].toInt(), args["y"_L1].toInt()) - scroll_pos));
 	} else if (msg == "pageInternalNav"_L1) {
 		Q_EMIT pageInternalNavigation(QUrl(args["href"_L1].toString()));
 	} else {
@@ -259,10 +309,10 @@ bool RKQWebView::supportsContentType(const QString &mimename) {
 
 void RKQWebView::zoomIn() {
 	RK_TRACE(APP);
-	// TODO
+	QMetaObject::invokeMethod(webView(), "doZoom", Q_ARG(int, 1));
 }
 
 void RKQWebView::zoomOut() {
 	RK_TRACE(APP);
-	// TODO
+	QMetaObject::invokeMethod(webView(), "doZoom", Q_ARG(int, -1));
 }
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index d37a8c126..069b0982f 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -31,7 +31,6 @@ class RKQWebView : public RKHTMLViewer {
 	void setHTML(const QString &html, const QUrl &url) override;
 	bool installHelpProtocolHandler() override;
 	void findRequest(const QString &text, bool backwards, RKFindBar *findbar, bool *found) override;
-	QMenu *createContextMenu(const QPoint &clickpos) override;
 	QString selectedText() const override;
 	void exportPage() override;
 	QPoint scrollPosition() const override;
@@ -44,6 +43,7 @@ class RKQWebView : public RKHTMLViewer {
 	friend class RKHTMLViewer;
 	friend class RKQWebViewCallbackServer;
 	RKQWebView(RKHTMLWindow *parent);
+	virtual ~RKQWebView();
 	QPointer<QQuickWidget> view;
 	QUrl currentAcceptedUrl() const;
 	QMap<QString, QString> persistentScripts;
@@ -57,6 +57,7 @@ class RKQWebView : public RKHTMLViewer {
 	void receivedCallbackMessage(const QString &message);
 	QString selected_text;
 	QPoint scroll_pos;
+	QMenu *context_menu;
   private Q_SLOTS:
 	void onUrlChanged(const QUrl &url, const QString &error, int status);
 	void onLoadFinished(const QUrl &url);
diff --git a/rkward/windows/rkqwebview.js b/rkward/windows/rkqwebview.js
index 79b465563..99cf45a95 100644
--- a/rkward/windows/rkqwebview.js
+++ b/rkward/windows/rkqwebview.js
@@ -31,4 +31,18 @@ document.addEventListener('click', e => {
 	const origin = e.target.closest('a');
 	if (origin && origin.href.startswith('#')) {
 		__rkward_sendMessage("pageInternalNav", { url: origin.href });
+		e.preventDefault();
+	}
+});
+
+document.addEventListener('contextmenu', e => {
+	let params = { x: e.screenX, y: e.screenY };
+	try {
+		params.url = e.target.closest('a').href;
+	} catch {}
+	try {
+		params.src = e.target.closest('img').src;
+	} catch {}
+	__rkward_sendMessage("contextMenu", params);
+	e.preventDefault();
 });
diff --git a/rkward/windows/rkqwebview.qml b/rkward/windows/rkqwebview.qml
index ca84b899d..4c7983dd3 100644
--- a/rkward/windows/rkqwebview.qml
+++ b/rkward/windows/rkqwebview.qml
@@ -25,6 +25,7 @@ Item {
                 visible: true
                 width: parent.width
                 height: parent.height
+                transformOrigin: Item.TopLeft
                 property string acceptedUrl: ""
                 property string requestedUrl: ""
 
@@ -46,5 +47,10 @@ Item {
                                 loadFinished(request.url);
                         }
                 }
+                function doZoom(dir: int) {
+                        scale += dir * .1;
+                        width = parent.width / scale;
+                        height = parent.height / scale;
+                }
         }
 }


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Implement_callback_from_qwebview_page_to_rkward=2C_handle_selection_as_proof_of_concept?=
Message-ID: <20260409194424.664ED1401C09@lerwini.kde.org>

Git commit 4e31189741f22071cf089e8683b8f136001433c0 by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Implement callback from qwebview page to rkward, handle selection as proof of concept

M  +1    -1    CMakeLists.txt
M  +3    -0    rkward/resources.qrc
M  +1    -1    rkward/windows/CMakeLists.txt
M  +86   -7    rkward/windows/rkqwebview.cpp
M  +10   -1    rkward/windows/rkqwebview.h
A  +24   -0    rkward/windows/rkqwebview.js

https://invent.kde.org/education/rkward/-/commit/4e31189741f22071cf089e8683b8f136001433c0

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 327cac8f0..9e4c80a63 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,7 +27,7 @@ INCLUDE(ECMAddAppIcon)
 INCLUDE(ECMMarkNonGuiExecutable)
 INCLUDE(FeatureSummary)
 
-FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS Widgets Core Xml Network Qml PrintSupport WebEngineWidgets WebView)
+FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS Widgets Core Xml Network Qml PrintSupport WebEngineWidgets WebView WebSockets)
 FIND_PACKAGE(KF6 6.0.0 REQUIRED COMPONENTS CoreAddons DocTools I18n XmlGui TextEditor WidgetsAddons Parts Config Notifications WindowSystem Archive BreezeIcons OPTIONAL_COMPONENTS Crash)
 FIND_PACKAGE(Gettext REQUIRED)
 
diff --git a/rkward/resources.qrc b/rkward/resources.qrc
index 19b89ffd0..4d64ad619 100644
--- a/rkward/resources.qrc
+++ b/rkward/resources.qrc
@@ -24,6 +24,9 @@ SPDX-License-Identifier: GPL-2.0-or-later
 <qresource prefix="/org.kde.syntax-highlighting/syntax-addons">
 	<file alias="rkward.xml">syntax/rkward.xml</file>
 </qresource>
+<qresource prefix="/js">
+	<file alias="rkqwebview.js">windows/rkqwebview.js</file>
+</qresource>
 <qresource prefix="/qml">
 	<file alias="rkqwebview.qml">windows/rkqwebview.qml</file>
 </qresource>
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index 2192b66a3..fb1f2a169 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -36,4 +36,4 @@ SET(windows_STAT_SRCS
 )
 
 ADD_LIBRARY(windows STATIC ${windows_STAT_SRCS})
-TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport Qt6::WebEngineWidgets Qt6::WebView Qt6::Qml Qt6::Quick Qt6::QuickWidgets KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
+TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport Qt6::WebEngineWidgets Qt6::WebView Qt6::WebSockets Qt6::Qml Qt6::Quick Qt6::QuickWidgets KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 208196fc1..0ac24e0a4 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -7,9 +7,12 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkqwebview.h"
 
+#include <QFile>
 #include <QMenu>
 #include <QQuickItem>
 #include <QQuickWidget>
+#include <QWebSocket>
+#include <QWebSocketServer>
 
 #include "../misc/rkfindbar.h"
 #include "rkhtmlwindow.h"
@@ -17,8 +20,56 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "../debug.h"
 
-RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr) {
+class RKQWebViewCallbackServer {
+  public:
+	static void initConnection(RKQWebView *window) {
+		static QWebSocketServer *server = nullptr;
+		if (!server) {
+			server = new QWebSocketServer(QString(), QWebSocketServer::NonSecureMode);
+			server->listen(QHostAddress::LocalHost);
+			QObject::connect(server, &QWebSocketServer::newConnection, server, [server]() {
+				auto con = server->nextPendingConnection();
+				const auto url = con->requestUrl().url();
+				auto win = windows.value(url);
+				RK_DEBUG(APP, DL_WARNING, "New connection at %s from window %p", qPrintable(url), win); // TODO
+				if (!win) {
+					con->abort();
+				}
+
+				// TODO handle connection lifetime
+
+				QObject::connect(con, &QWebSocket::textMessageReceived, win, [win](const QString &message) {
+					win->receivedCallbackMessage(message);
+				});
+			});
+		}
+
+		// Window may already be in our map. We need to re-init on every url change
+		auto key = windows.key(window);
+		if (key.isEmpty()) {
+			key = server->serverUrl().url() + u"/"_s + QUuid::createUuid().toString(QUuid::Id128);
+			windows.insert(key, window);
+			QObject::connect(window, &QObject::destroyed, server, [key]() {
+				windows.remove(key);
+			});
+		}
+
+		window->runJS(u"let __rkward = new WebSocket('%1');"_s.arg(key));
+	}
+
+  protected:
+	static QHash<QString, RKQWebView *> windows;
+};
+QHash<QString, RKQWebView *> RKQWebViewCallbackServer::windows;
+
+RKQWebView::RKQWebView(RKHTMLWindow *parent) : RKHTMLViewer(parent), view(nullptr), running_script(false) {
 	RK_TRACE(APP);
+	QFile file(u":/js/rkqwebview.js"_s);
+	if (file.open(QIODevice::ReadOnly)) {
+		installPersistentJS(QString::fromUtf8(file.readAll()), file.fileName());
+	} else {
+		RK_ASSERT(false);
+	}
 }
 
 QUrl RKQWebView::currentAcceptedUrl() const {
@@ -45,6 +96,8 @@ void RKQWebView::onUrlChanged(const QUrl &_url, const QString &error, int status
 void RKQWebView::onLoadFinished(const QUrl &url) {
 	RK_TRACE(APP);
 	RK_ASSERT(currentAcceptedUrl() == url);
+	selected_text.clear();
+	RKQWebViewCallbackServer::initConnection(this);
 	for (const auto &script : std::as_const(persistentScripts)) {
 		RKHTMLViewer::runJS(script);
 	}
@@ -108,15 +161,25 @@ void RKQWebView::installPersistentJS(const QString &script, const QString &id) {
 
 void RKQWebView::runJS(const QString &script, std::function<void(const QVariant &)> callback) {
 	RK_TRACE(APP);
-	RK_ASSERT(runjs_callback == nullptr); // no nested calls, please!
-	runjs_callback = callback;
-	QMetaObject::invokeMethod(webView(), "runJSWrapper", Q_ARG(QString, script));
+	runjs_queue.append({script, callback});
+	tryRunNextScript();
+}
+
+void RKQWebView::tryRunNextScript() {
+	RK_TRACE(APP);
+	if (running_script) return;
+	if (runjs_queue.isEmpty()) return;
+	running_script = true;
+	QMetaObject::invokeMethod(webView(), "runJSWrapper", Q_ARG(QString, runjs_queue.first().script));
 }
 
 void RKQWebView::onRunJSResult(const QVariant &result) {
 	RK_TRACE(APP);
-	if (runjs_callback != nullptr) runjs_callback(result);
-	runjs_callback = std::function<void(const QVariant &)>();
+	RK_ASSERT(!runjs_queue.isEmpty());
+	running_script = false;
+	const auto callback = runjs_queue.takeFirst().callback;
+	if (callback != nullptr) callback(result);
+	tryRunNextScript();
 }
 
 void RKQWebView::setHTML(const QString &html, const QUrl &_url) {
@@ -151,7 +214,23 @@ QMenu *RKQWebView::createContextMenu(const QPoint &clickpos) {
 QString RKQWebView::selectedText() const {
 	RK_TRACE(APP);
 
-	return QString();
+	return selected_text;
+}
+
+void RKQWebView::receivedCallbackMessage(const QString &message) {
+	RK_TRACE(APP);
+	const auto mo = QJsonDocument::fromJson(message.toUtf8()).object();
+	const auto msg = mo["msg"_L1].toString();
+	const auto args = mo["args"_L1];
+	if (msg == "selChanged"_L1) {
+		const auto sel = args["sel"_L1].toString();
+		if (sel != selected_text) {
+			selected_text = sel;
+			Q_EMIT selectionChanged(!sel.isEmpty());
+		}
+	} else {
+		RK_DEBUG(APP, DL_ERROR, "Non-recognized message %s", qPrintable(message));
+	}
 }
 
 void RKQWebView::exportPage() {
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index f681105c5..c6b73ada5 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -41,11 +41,20 @@ class RKQWebView : public RKHTMLViewer {
 
   private:
 	friend class RKHTMLViewer;
+	friend class RKQWebViewCallbackServer;
 	RKQWebView(RKHTMLWindow *parent);
 	QPointer<QQuickWidget> view;
 	QUrl currentAcceptedUrl() const;
 	QMap<QString, QString> persistentScripts;
-	std::function<void(const QVariant &)> runjs_callback;
+	struct Script {
+		QString script;
+		std::function<void(const QVariant &)> callback;
+	};
+	QList<Script> runjs_queue;
+	bool running_script;
+	void tryRunNextScript();
+	void receivedCallbackMessage(const QString &message);
+	QString selected_text;
   private Q_SLOTS:
 	void onUrlChanged(const QUrl &url, const QString &error, int status);
 	void onLoadFinished(const QUrl &url);
diff --git a/rkward/windows/rkqwebview.js b/rkward/windows/rkqwebview.js
new file mode 100644
index 000000000..3c9473e6f
--- /dev/null
+++ b/rkward/windows/rkqwebview.js
@@ -0,0 +1,24 @@
+// rkqwebview.js - This file is part of the RKWard project. Created: Sat Mar 14 2026
+// SPDX-FileCopyrightText: 2026 by Thomas Friedrichsmeier <thomas.friedrichsmeier at kdemail.net>
+// SPDX-FileContributor: The RKWard Team <rkward at kde.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+function __rkward_debounce(fun) {
+	let to = null;
+	return function() {
+		clearTimeout(to);
+		to = setTimeout(function() { fun(); }, 20);
+	}
+}
+
+document.onselectionchange = __rkward_debounce(function() {
+	__rkward_sendMessage("selChanged", { sel: document.getSelection().toString() });
+});
+
+function __rkward_sendMessage(msg, args) {
+	if (__rkward.readyState == WebSocket.CONNECTING) {
+		setTimeout(__rkward_sendMessage.bind(null, msg, args), 10);
+	} else {
+		__rkward.send(JSON.stringify({msg, args}));
+	}
+}


From null at kde.org  Thu Apr  9 20:44:24 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 19:44:24 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Allow_to_configure_HTML_engine_to_use?=
Message-ID: <20260409194424.69B4F1401D24@lerwini.kde.org>

Git commit f989f196d2c2d78482fd35109d8354b3aa4e9470 by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 19:44.
Pushed by tfry into branch 'master'.

Allow to configure HTML engine to use

(at compiletime and at runtime)

M  +1    -1    .gitlab-ci.yml
M  +45   -2    CMakeLists.txt
M  +1    -0    ChangeLog
M  +8    -1    INSTALL
M  +2    -3    rkward/CMakeLists.txt
M  +23   -8    rkward/main.cpp
M  +1    -1    rkward/rkward.cpp
M  +1    -0    rkward/settings/CMakeLists.txt
M  +22   -3    rkward/settings/rksettingsmodulegeneral.cpp
M  +8    -0    rkward/settings/rksettingsmodulegeneral.h
M  +8    -1    rkward/windows/CMakeLists.txt
M  +21   -3    rkward/windows/rkhtmlviewer.cpp
M  +36   -34   rkward/windows/rkqwebenginewidget.cpp
M  +4    -2    rkward/windows/rkqwebenginewidget.h
M  +16   -14   rkward/windows/rkqwebview.cpp
M  +6    -4    rkward/windows/rkqwebview.h
M  +8    -6    rkward/windows/rkqwebview.js

https://invent.kde.org/education/rkward/-/commit/f989f196d2c2d78482fd35109d8354b3aa4e9470

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1cf92fcc9..e994d096e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,7 +16,7 @@ include:
 #      - /gitlab-templates/flatpak.yml // just a waste of CPU power in the current state
 
 # NOTE: This is meant to modify the default linux-qt6.yml-job, and currently it does. It will need to be updated once the underlying name changes, though
-suse_tumbleweed_qt610:
+suse_tumbleweed_qt611:
   script:
     - git config --global --add safe.directory $CI_PROJECT_DIR
     # no dlopen() loading, to get at better symbol information for the backend process from ASAN
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9e4c80a63..96d0472c0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,10 +27,47 @@ INCLUDE(ECMAddAppIcon)
 INCLUDE(ECMMarkNonGuiExecutable)
 INCLUDE(FeatureSummary)
 
-FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS Widgets Core Xml Network Qml PrintSupport WebEngineWidgets WebView WebSockets)
+OPTION(FORCE_WITH_QWEBENGINE "Build QWebEngine based HTML renderer (overrides automatic selection)" OFF)
+ADD_FEATURE_INFO("FORCE_WITH_QWEBENGINE: Build QWebEngine based HTML renderer" FORCE_WITH_QWEBENGINE "Override automatic detection of with HTML render to build for. Setting this option to ON implies that no other HTML renderer support is compiled, unless it is also specified with the corresponding option (see FORCE_WITH_QWEBVIEW).")
+
+OPTION(FORCE_WITH_QWEBVIEW "Build QWebView based HTML renderer (overrides automatic selection)" OFF)
+ADD_FEATURE_INFO("FORCE_WITH_QWEBVIEW: Build QWebView based HTML renderer" FORCE_WITH_QWEBVIEW "Override automatic detection of with HTML render to build for. Setting this option to ON implies that no other HTML renderer support is compiled, unless it is also specified with the corresponding option (see FORCE_WITH_QWEBENGINE).")
+
+OPTION(DLOPEN_RLIB "dlopen the R library at runtime rather than linking it" ON)
+ADD_FEATURE_INFO("DLOPEN_RLIB: dlopen() R library" DLOPEN_RLIB "This feature allows more flexibility in using a different R installation at runtime than at compile time.")
+
+SET(REQUIRED_QT6_LIBS Widgets Core Xml Network Qml PrintSupport)
+SET(OPTIONAL_QT6_LIBS)
+IF(FORCE_WITH_QWEBENGINE)
+  LIST(APPEND REQUIRED_QT6_LIBS WebEngineWidgets)
+ELSE()
+  LIST(APPEND OPTIONAL_QT6_LIBS WebEngineWidgets)
+ENDIF()
+IF(FORCE_WITH_QWEBVIEW)
+  LIST(APPEND REQUIRED_QT6_LIBS WebView WebSockets)
+ELSE()
+  LIST(APPEND OPTIONAL_QT6_LIBS WebView WebSockets)
+ENDIF()
+
+FIND_PACKAGE(Qt6 6.6 CONFIG REQUIRED COMPONENTS ${REQUIRED_QT6_LIBS} OPTIONAL_COMPONENTS ${OPTIONAL_QT6_LIBS})
 FIND_PACKAGE(KF6 6.0.0 REQUIRED COMPONENTS CoreAddons DocTools I18n XmlGui TextEditor WidgetsAddons Parts Config Notifications WindowSystem Archive BreezeIcons OPTIONAL_COMPONENTS Crash)
 FIND_PACKAGE(Gettext REQUIRED)
 
+if(Qt6WebEngineWidgets_FOUND AND (${FORCE_WITH_QWEBENGINE} OR NOT ${FORCE_WITH_QWEBVIEW}))
+  set(WITH_QWEBENGINE ON)
+  set(RK_QWEBENGINE_FLAGS -DRK_WITH_QWEBENGINE=1)
+else()
+  set(WITH_QWEBENGINE OFF)
+  set(RK_QWEBENGINE_FLAGS -DRK_WITH_QWEBENGINE=0)
+endif()
+if(Qt6WebView_FOUND AND Qt6WebSockets_FOUND AND (${FORCE_WITH_QWEBVIEW} OR NOT ${FORCE_WITH_QWEBENGINE}))
+  set(WITH_QWEBVIEW ON)
+  set(RK_QWEBVIEW_FLAGS -DRK_WITH_QWEBVIEW=1)
+else()
+  set(WITH_QWEBVIEW OFF)
+  set(RK_QWEBVIEW_FLAGS -DRK_WITH_QWEBVIEW=0)
+endif()
+
 if(NOT WIN32) # will apparently pick up trash, on Windows, sometimes
   FIND_PACKAGE(KDSingleApplication-qt6 1.1.0 QUIET)
 endif()
@@ -57,4 +94,10 @@ ADD_SUBDIRECTORY(tests)
 KI18N_INSTALL(po)
 KDOCTOOLS_INSTALL(po)
 
-FEATURE_SUMMARY(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
+FEATURE_SUMMARY(WHAT ENABLED_FEATURES DISABLED_FEATURES)
+FEATURE_SUMMARY(WHAT PACKAGES_FOUND PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES)
+message(STATUS "QWebView support: ${WITH_QWEBVIEW}")
+message(STATUS "QWebEngine support: ${WITH_QWEBENGINE}")
+if(NOT (WITH_QWEBVIEW OR WITH_QWEBENGINE))
+  message(FATAL_ERROR "Found no suitable HTML renderer. Please install either the QWebEngineWidgets library or QWebView and QWebSockets.")
+endif()
diff --git a/ChangeLog b/ChangeLog
index ba5649f65..8e45e5b73 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,5 @@
 --- Version 0.8.3 - UNRELEASED
+- Added: Support HTML rendering with QWebView, which allows to use native renderers on some platforms
 - Fixed: Crash when splitting view on a data.frame editor window
 - Fixed: Allow compilation with the upcoming R 4.6
 - Fixed: Horizontal text alignment would be inaccurate for some symbols in RK() device
diff --git a/INSTALL b/INSTALL
index fa927ef4f..44c73378a 100644
--- a/INSTALL
+++ b/INSTALL
@@ -43,9 +43,16 @@ In some cases you may want to set the following options:
 -DUSE_BINARY_PACKAGES
 	Currently only interpreted on Mac OS: If RKWard should default to installing binary R packages (if available) use
 	-DUSE_BINARY_PACKAGE=1. Otherwise, RKWard will default to building R packages from source.
--DDLOPEN_RLIB
+-DDLOPEN_RLIB=OFF
 	By default, the backend code is loaded using dlopen() to avoid certain runtime conflicts, esp. in distributed AppImages. This, however, makes it hard to
 	get at proper debug symbol information. For debugging purposes it may thus be useful to set this to "0", explictly
+-DFORCE_WITH_QWEBENGINE=ON
+	RKWard supports two HTML rendering libraries, QWebEngine or QWebView. By default, either - or both - will be selected automatically, based on the
+	presence of the required libaries. Use this (and/or -DFORCE_WOTH_QWEBVIEW) to override the automatic selction. Setting either öf these options to ON
+	will disable building against the other library *unless* that is also specified with FORCE_WITH_XYZ.
+	Packagers please note that, on Linux, using QWebView currently still requires a runtime dependency on QWebEngine, as no native rendering plugin is
+	available in Qt at the time of this writing.
+-DFORCE_WITH_QWEBVIEW=ON
 
 Further generic options are listed on http://www.cmake.org/Wiki/CMake_Useful_Variables .
 
diff --git a/rkward/CMakeLists.txt b/rkward/CMakeLists.txt
index 4908273b1..f841df621 100644
--- a/rkward/CMakeLists.txt
+++ b/rkward/CMakeLists.txt
@@ -8,9 +8,6 @@ ENDIF(NOT ${PROJECT_NAME} STREQUAL "rkward")
 
 INCLUDE(SetVersionNumber.cmake)
 
-OPTION(DLOPEN_RLIB "dlopen the R library at runtime rather than linking it" ON)
-ADD_FEATURE_INFO("DLOPEN_RLIB: dlopen() R library" DLOPEN_RLIB "This feature allows more flexibility in using a different R installation at runtime than at compile time.")
-
 ADD_SUBDIRECTORY( dataeditor )
 ADD_SUBDIRECTORY( settings )
 ADD_SUBDIRECTORY( plugin )
@@ -55,6 +52,8 @@ ADD_EXECUTABLE(rkward ${RKWard_App_Sources})
 TARGET_COMPILE_DEFINITIONS(rkward PUBLIC -DINSTALL_PATH="${CMAKE_INSTALL_PREFIX}")
 FILE(RELATIVE_PATH RelPathToData /${KDE_INSTALL_BINDIR} /${KDE_INSTALL_DATADIR})
 TARGET_COMPILE_DEFINITIONS(rkward PUBLIC -DREL_PATH_TO_DATA="${RelPathToData}")
+TARGET_COMPILE_DEFINITIONS(rkward PUBLIC ${RK_QWEBENGINE_FLAGS} ${RK_QWEBVIEW_FLAGS})
+TARGET_COMPILE_DEFINITIONS(rkward_lib PUBLIC ${RK_QWEBENGINE_FLAGS} ${RK_QWEBVIEW_FLAGS})
 TARGET_LINK_LIBRARIES(rkward rkward_lib)
 IF(KF6Crash_FOUND)
 	TARGET_LINK_LIBRARIES(rkward KF6::Crash)
diff --git a/rkward/main.cpp b/rkward/main.cpp
index 61b494637..ada3a2d31 100644
--- a/rkward/main.cpp
+++ b/rkward/main.cpp
@@ -59,7 +59,6 @@ SPDX-License-Identifier: GPL-2.0-or-later
 #include <QThread>
 #include <QTime>
 #include <QUrl>
-#include <QtWebView>
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -136,26 +135,42 @@ void RKDebug(int flags, int level, const char *fmt, ...) {
 	}
 }
 
-#include <QWebEngineUrlScheme>
-
-int main(int argc, char *argv[]) {
-	RK_Debug::RK_Debug_Level = DL_WARNING;
-#if defined(Q_OS_MACOS)
+#if RK_WITH_QWEBENGINE
+#	include <QWebEngineUrlScheme>
+static void setupWebEngine() {
+#	if defined(Q_OS_MACOS)
 	// TODO: This is just a hackish workaround. See https://invent.kde.org/education/rkward/-/issues/28
 	const char *chromiumflags = "QTWEBENGINE_CHROMIUM_FLAGS";
 	if (!qEnvironmentVariableIsSet(chromiumflags)) {
 		qputenv(chromiumflags, "--no-sandbox --single-process --enable-features=NetworkServiceInProcess");
 	}
-#endif
+#	endif
 
 	// annoyingly, QWebEngineUrlSchemes have to be registered before creating the app.
 	QWebEngineUrlScheme scheme("help");
 	scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path);
 	scheme.setFlags(QWebEngineUrlScheme::LocalAccessAllowed);
 	QWebEngineUrlScheme::registerScheme(scheme);
+}
+#else
+static void setupWebEngine() {}
+#endif
+
+#if RK_WITH_QWEBVIEW
+#	include <QtWebView>
+static void setupWebView() {
+	QtWebView::initialize();
+}
+#else
+static void setupWebView() {}
+#endif
+
+int main(int argc, char *argv[]) {
+	RK_Debug::RK_Debug_Level = DL_WARNING;
+	setupWebEngine();
+	setupWebView();
 	BreezeIcons::initIcons(); // install as fallback theme. Too many issues with missing icons, otherwise
 	QApplication app(argc, argv);
-	QtWebView::initialize();
 	KDSingleApplication app_singleton;
 #if defined(Q_OS_MACOS) || defined(Q_OS_WIN)
 	// Follow the example of kate, and use breeze theme on Windows and Mac, which appears to work best
diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp
index f9cdcdb18..b23300e17 100644
--- a/rkward/rkward.cpp
+++ b/rkward/rkward.cpp
@@ -185,7 +185,7 @@ RKWardMainWindow::RKWardMainWindow() : KParts::MainWindow() {
 	RKComponentMap::initialize();
 
 	// stuff which should wait until the event loop is running
-#if defined(Q_OS_WIN)
+#if RK_WITH_QWEBENGINE && defined(Q_OS_WIN)
 	// HACK FIXME
 	// This is a very bad workaround around a very hideous QtWebEngine bug on Windows. Surfaced for us with the switch to Qt 6.9:
 	// crash in the QWebEnginePage c'tor, if that is invoked too soon after start *and* application was not started from the
diff --git a/rkward/settings/CMakeLists.txt b/rkward/settings/CMakeLists.txt
index 6ab40365a..516ebbad3 100644
--- a/rkward/settings/CMakeLists.txt
+++ b/rkward/settings/CMakeLists.txt
@@ -34,4 +34,5 @@ IF (WIN32 OR USE_BINARY_PACKAGES)
 ENDIF (WIN32 OR USE_BINARY_PACKAGES)
 
 ADD_LIBRARY(settings STATIC ${settings_STAT_SRCS})
+TARGET_COMPILE_DEFINITIONS(settings PUBLIC ${RK_QWEBENGINE_FLAGS} ${RK_QWEBVIEW_FLAGS})
 TARGET_LINK_LIBRARIES(settings Qt6::Widgets KF6::WidgetsAddons Qt6::Xml KF6::ConfigCore KF6::Parts KF6::TextEditor KF6::I18n KF6::KIOWidgets)
diff --git a/rkward/settings/rksettingsmodulegeneral.cpp b/rkward/settings/rksettingsmodulegeneral.cpp
index ab9b46555..fdac86167 100644
--- a/rkward/settings/rksettingsmodulegeneral.cpp
+++ b/rkward/settings/rksettingsmodulegeneral.cpp
@@ -50,6 +50,7 @@ bool RKSettingsModuleGeneral::rkward_version_changed;
 bool RKSettingsModuleGeneral::installation_moved = false;
 QString RKSettingsModuleGeneral::previous_rkward_data_dir;
 RKConfigValue<int> RKSettingsModuleGeneral::num_recent_files{"Max number of recent files", 8};
+RKConfigValue<RKSettingsModuleGeneral::HTMLEngine, int> RKSettingsModuleGeneral::html_engine("HTML rendering engine", DefaultRenderingEngine);
 RKConfigValue<bool> RKSettingsModuleGeneral::disable_hardware_rendering{"disable_hardware_rendering", true};
 
 class RKSettingsPageGeneral : public RKSettingsModuleWidget {
@@ -71,9 +72,6 @@ class RKSettingsPageGeneral : public RKSettingsModuleWidget {
 		auto vbox = new QVBoxLayout(group);
 		vbox->addWidget(RKSettingsModuleGeneral::autorestore_from_wd.makeCheckbox(i18n("Load .RData-file from startup directory, if available (R option '--restore')"), this));
 		vbox->addWidget(RKSettingsModuleGeneral::show_help_on_startup.makeCheckbox(i18n("Show RKWard Help on Startup"), this));
-		auto cb = RKSettingsModuleGeneral::disable_hardware_rendering.makeCheckbox(i18n("Disable HTML hardware rendering (setting takes effect after restarting RKWard)"), this);
-		RKCommonFunctions::setTips(i18n("The use of hardware acceleration for HTML rendering (e.g. help pages and plugin output) is known to cause instability in some installations, and is not generally needed for the - usually simple - content shown in RKWard. It is recommended to leave hardware acceleration disabled, unless you experience performance problems while viewing HTML content inside RKWard."), cb);
-		vbox->addWidget(cb);
 
 		QGroupBox *group_box = new QGroupBox(i18n("Initial working directory"), this);
 		QHBoxLayout *hlayout = new QHBoxLayout(group_box);
@@ -93,6 +91,26 @@ class RKSettingsPageGeneral : public RKSettingsModuleWidget {
 		vbox->addWidget(group_box);
 		main_vbox->addWidget(group);
 
+		group = new QGroupBox(i18n("HTML rendering (setings take effect after restarting RKWard)"));
+		hlayout = new QHBoxLayout(group);
+		hlayout->addWidget(new QLabel(i18n("Engine:")));
+		auto dd = RKSettingsModuleGeneral::html_engine.makeDropDown(RKConfigBase::LabelList({{RKSettingsModuleGeneral::DefaultRenderingEngine, i18n("Platform default")},
+#if RK_WITH_QWEBENGINE
+		                                                                                     {RKSettingsModuleGeneral::QWebEngineRenderingEngine, i18n("QWebEngine")},
+#endif
+#if RK_WITH_QWEBVIEW
+		                                                                                     { RKSettingsModuleGeneral::QWebViewRenderingEngine,
+			                                                                                   i18n("QWebView") }
+#endif
+		                                                            }),
+		                                                            this);
+		hlayout->addWidget(dd);
+		hlayout->addStretch();
+		auto cb = RKSettingsModuleGeneral::disable_hardware_rendering.makeCheckbox(i18n("Disable hardware acceleration"), this);
+		RKCommonFunctions::setTips(i18n("The use of hardware acceleration for HTML rendering (e.g. help pages and plugin output) is known to cause instability in some installations, and is not generally needed for the - usually simple - content shown in RKWard. It is recommended to leave hardware acceleration disabled, unless you experience performance problems while viewing HTML content inside RKWard."), cb);
+		hlayout->addWidget(cb);
+		main_vbox->addWidget(group);
+
 		main_vbox->addSpacing(2 * RKStyle::spacingHint());
 
 		auto num_recent_files_box = RKSettingsModuleGeneral::num_recent_files.makeSpinBox(1, INT_MAX, this);
@@ -208,6 +226,7 @@ void RKSettingsModuleGeneral::syncConfig(KConfig *config, RKConfigBase::ConfigSy
 	num_recent_files.syncConfig(cg, a);
 	initial_dir.syncConfig(cg, a);
 	disable_hardware_rendering.syncConfig(cg, a);
+	html_engine.syncConfig(cg, a);
 	if ((a == RKConfigBase::SaveConfig) && (initial_dir == LastUsedDirectory)) {
 		cg.writeEntry(initial_dir_specification.key(), QDir::currentPath());
 	} else {
diff --git a/rkward/settings/rksettingsmodulegeneral.h b/rkward/settings/rksettingsmodulegeneral.h
index d82ba54c6..683d55d06 100644
--- a/rkward/settings/rksettingsmodulegeneral.h
+++ b/rkward/settings/rksettingsmodulegeneral.h
@@ -42,6 +42,12 @@ class RKSettingsModuleGeneral : public RKSettingsModule {
 		RKMDIFocusFollowsMouse = 1
 	};
 
+	enum HTMLEngine {
+		DefaultRenderingEngine,
+		QWebEngineRenderingEngine,
+		QWebViewRenderingEngine
+	};
+
 	void syncConfig(KConfig *config, RKConfigBase::ConfigSyncAction) override;
 	void createPages(RKSettings *parent) override;
 	static constexpr PageId page_id = QLatin1String("general");
@@ -50,6 +56,7 @@ class RKSettingsModuleGeneral : public RKSettingsModule {
 	static QString &filesPath() { return files_path; };
 	static bool showHelpOnStartup() { return show_help_on_startup; };
 	static bool disableHardwareRendering() { return disable_hardware_rendering; };
+	static HTMLEngine htmlEngine() { return html_engine; };
 	static bool openRestoreFileOnLoad() { return autorestore_from_wd; };
 	static WorkplaceSaveMode workplaceSaveMode() { return workplace_save_mode; };
 	/** retrieve the saved workplace description. Meaningful only is workplaceSaveMode () == SaveWorkplaceWithSession */
@@ -109,6 +116,7 @@ class RKSettingsModuleGeneral : public RKSettingsModule {
 	static bool installation_moved;
 	static RKConfigValue<int> num_recent_files;
 	static RKConfigValue<bool> disable_hardware_rendering;
+	static RKConfigValue<HTMLEngine, int> html_engine;
 };
 
 #endif
diff --git a/rkward/windows/CMakeLists.txt b/rkward/windows/CMakeLists.txt
index fb1f2a169..708514e20 100644
--- a/rkward/windows/CMakeLists.txt
+++ b/rkward/windows/CMakeLists.txt
@@ -36,4 +36,11 @@ SET(windows_STAT_SRCS
 )
 
 ADD_LIBRARY(windows STATIC ${windows_STAT_SRCS})
-TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport Qt6::WebEngineWidgets Qt6::WebView Qt6::WebSockets Qt6::Qml Qt6::Quick Qt6::QuickWidgets KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
+TARGET_COMPILE_DEFINITIONS(windows PUBLIC ${RK_QWEBENGINE_FLAGS} ${RK_QWEBVIEW_FLAGS})
+TARGET_LINK_LIBRARIES(windows Qt6::Widgets Qt6::PrintSupport KF6::TextEditor KF6::Notifications KF6::WindowSystem KF6::KIOFileWidgets KF6::I18n)
+if(WITH_QWEBENGINE)
+	TARGET_LINK_LIBRARIES(windows Qt6::WebEngineWidgets)
+endif()
+if(WITH_QWEBVIEW)
+	TARGET_LINK_LIBRARIES(windows Qt6::WebView Qt6::WebSockets Qt6::Qml Qt6::Quick Qt6::QuickWidgets)
+endif()
diff --git a/rkward/windows/rkhtmlviewer.cpp b/rkward/windows/rkhtmlviewer.cpp
index 0176a625e..766e9e071 100644
--- a/rkward/windows/rkhtmlviewer.cpp
+++ b/rkward/windows/rkhtmlviewer.cpp
@@ -7,6 +7,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "rkhtmlviewer.h"
 
+#include "../settings/rksettingsmodulegeneral.h"
 #include "rkhtmlwindow.h"
 #include "rkqwebenginewidget.h"
 #include "rkqwebview.h"
@@ -19,8 +20,25 @@ RKHTMLViewer::RKHTMLViewer(RKHTMLWindow *parent) : QObject(parent), window(paren
 
 RKHTMLViewer *RKHTMLViewer::getNew(RKHTMLWindow *parent) {
 	RK_TRACE(APP);
-	//return new RKQWebEngineWidget(parent);
+	const auto engine = RKSettingsModuleGeneral::htmlEngine();
+#if RK_WITH_QWEBENGINE
+	if (engine == RKSettingsModuleGeneral::QWebEngineRenderingEngine) {
+		return new RKQWebEngineWidget(parent);
+	}
+#endif
+#if RK_WITH_QWEBVIEW
+	if (engine == RKSettingsModuleGeneral::QWebViewRenderingEngine) {
+		return new RKQWebView(parent);
+	}
+#endif
+	// default to QWebEngine on Linux (where it mostly works), but to QWebView
+	// everywhere else (if possible)
+#if defined(Q_OS_LINUX) && RK_WITH_QWEBENGINE
+	return new RKQWebEngineWidget(parent);
+#endif
+#if RK_WITH_QWEBVIEW
 	return new RKQWebView(parent);
+#else
+	return new RKQWebEngineWidget(parent);
+#endif
 }
-
-#include "rkhtmlviewer.moc"
diff --git a/rkward/windows/rkqwebenginewidget.cpp b/rkward/windows/rkqwebenginewidget.cpp
index d975129fc..6f71813f2 100644
--- a/rkward/windows/rkqwebenginewidget.cpp
+++ b/rkward/windows/rkqwebenginewidget.cpp
@@ -5,36 +5,37 @@ SPDX-FileContributor: The RKWard Team <rkward at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
 
-#include "rkqwebenginewidget.h"
-
-#include <QBuffer>
-#include <QDir>
-#include <QFileDialog>
-#include <QFontDatabase>
-#include <QMenu>
-#include <QMimeDatabase>
-#include <QPrintDialog>
-#include <QWebEngineFindTextResult>
-#include <QWebEnginePage>
-#include <QWebEngineProfile>
-#include <QWebEngineScript>
-#include <QWebEngineScriptCollection>
-#include <QWebEngineSettings>
-#include <QWebEngineUrlRequestJob>
-#include <QWebEngineUrlSchemeHandler>
-#include <QWebEngineView>
-#include <QWheelEvent>
-
-#include <KIO/StoredTransferJob>
-#include <KLocalizedString>
-
-#include "../misc/rkfindbar.h"
-#include "../rkward.h"
-#include "../settings/rksettingsmoduler.h"
-#include "rkhtmlwindow.h"
-#include "rkworkplace.h"
-
-#include "../debug.h"
+#if RK_WITH_QWEBENGINE
+#	include "rkqwebenginewidget.h"
+
+#	include <QBuffer>
+#	include <QDir>
+#	include <QFileDialog>
+#	include <QFontDatabase>
+#	include <QMenu>
+#	include <QMimeDatabase>
+#	include <QPrintDialog>
+#	include <QWebEngineFindTextResult>
+#	include <QWebEnginePage>
+#	include <QWebEngineProfile>
+#	include <QWebEngineScript>
+#	include <QWebEngineScriptCollection>
+#	include <QWebEngineSettings>
+#	include <QWebEngineUrlRequestJob>
+#	include <QWebEngineUrlSchemeHandler>
+#	include <QWebEngineView>
+#	include <QWheelEvent>
+
+#	include <KIO/StoredTransferJob>
+#	include <KLocalizedString>
+
+#	include "../misc/rkfindbar.h"
+#	include "../rkward.h"
+#	include "../settings/rksettingsmoduler.h"
+#	include "rkhtmlwindow.h"
+#	include "rkworkplace.h"
+
+#	include "../debug.h"
 
 // TODO: remove dupe
 static QUrl restorableUrl(const QUrl &url) {
@@ -128,10 +129,10 @@ class RKWebPage : public QWebEnginePage {
 		// sigh: acceptNavigationRequest() does  not get called on the new page...
 		QMetaObject::Connection *const connection = new QMetaObject::Connection;
 		*connection = connect(ret, &RKWebPage::loadStarted, [ret, connection
-#ifdef _MSC_VER
+#	ifdef _MSC_VER
 		                                                     ,
 		                                                     this // capturing "this" makes MSVC happy
-#endif
+#	endif
 		]() {
 			QObject::disconnect(*connection);
 			delete connection;
@@ -209,8 +210,8 @@ RKQWebEngineWidget::RKQWebEngineWidget(RKHTMLWindow *parent) : RKHTMLViewer(pare
 
 	if (RKWebPage::opened_as_new) {
 		page = RKWebPage::opened_as_new;
-		RKWebPage::opened_as_new = nullptr;
 		RKWebPage::opened_as_new->window = parent;
+		RKWebPage::opened_as_new = nullptr;
 	} else {
 		page = new RKWebPage(parent);
 	}
@@ -381,4 +382,5 @@ void RKQWebEngineWidget::findRequest(const QString &text, bool backwards, RKFind
 	});
 }
 
-#include "rkqwebenginewidget.moc"
+#	include "rkqwebenginewidget.moc"
+#endif
diff --git a/rkward/windows/rkqwebenginewidget.h b/rkward/windows/rkqwebenginewidget.h
index 998384bfe..77eb2ea51 100644
--- a/rkward/windows/rkqwebenginewidget.h
+++ b/rkward/windows/rkqwebenginewidget.h
@@ -7,10 +7,11 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #ifndef RKQWEBENGINEWIDGET_H
 #define RKQWEBENGINEWIDGET_H
+#if RK_WITH_QWEBENGINE
 
-#include <QPointer>
+#	include <QPointer>
 
-#include "rkhtmlviewer.h"
+#	include "rkhtmlviewer.h"
 
 class RKWebPage;
 class QWebEngineView;
@@ -43,3 +44,4 @@ class RKQWebEngineWidget : public RKHTMLViewer {
 };
 
 #endif
+#endif
diff --git a/rkward/windows/rkqwebview.cpp b/rkward/windows/rkqwebview.cpp
index 78f0a67fa..72804fa64 100644
--- a/rkward/windows/rkqwebview.cpp
+++ b/rkward/windows/rkqwebview.cpp
@@ -5,24 +5,25 @@ SPDX-FileContributor: The RKWard Team <rkward at kde.org>
 SPDX-License-Identifier: GPL-2.0-or-later
 */
 
-#include "rkqwebview.h"
+#if RK_WITH_QWEBVIEW
+#	include "rkqwebview.h"
 
-#include <KLocalizedString>
-#include <KStandardActions>
+#	include <KLocalizedString>
+#	include <KStandardActions>
 
-#include <QClipboard>
-#include <QFile>
-#include <QMenu>
-#include <QQuickItem>
-#include <QQuickWidget>
-#include <QWebSocket>
-#include <QWebSocketServer>
+#	include <QClipboard>
+#	include <QFile>
+#	include <QMenu>
+#	include <QQuickItem>
+#	include <QQuickWidget>
+#	include <QWebSocket>
+#	include <QWebSocketServer>
 
-#include "../misc/rkfindbar.h"
-#include "rkhtmlwindow.h"
-#include "rkworkplace.h"
+#	include "../misc/rkfindbar.h"
+#	include "rkhtmlwindow.h"
+#	include "rkworkplace.h"
 
-#include "../debug.h"
+#	include "../debug.h"
 
 class RKQWebViewCallbackServer {
   public:
@@ -316,3 +317,4 @@ void RKQWebView::zoomOut() {
 	RK_TRACE(APP);
 	QMetaObject::invokeMethod(webView(), "doZoom", Q_ARG(int, -1));
 }
+#endif
diff --git a/rkward/windows/rkqwebview.h b/rkward/windows/rkqwebview.h
index 069b0982f..8600c1bc5 100644
--- a/rkward/windows/rkqwebview.h
+++ b/rkward/windows/rkqwebview.h
@@ -7,12 +7,13 @@ SPDX-License-Identifier: GPL-2.0-or-later
 
 #ifndef RKQWEBVIEW_H
 #define RKQWEBVIEW_H
+#if RK_WITH_QWEBVIEW
 
-#include <QMap>
-#include <QPoint>
-#include <QPointer>
+#	include <QMap>
+#	include <QPoint>
+#	include <QPointer>
 
-#include "rkhtmlviewer.h"
+#	include "rkhtmlviewer.h"
 
 class QQuickWidget;
 class QQuickItem;
@@ -66,3 +67,4 @@ class RKQWebView : public RKHTMLViewer {
 };
 
 #endif
+#endif
diff --git a/rkward/windows/rkqwebview.js b/rkward/windows/rkqwebview.js
index 99cf45a95..a18a8b8ac 100644
--- a/rkward/windows/rkqwebview.js
+++ b/rkward/windows/rkqwebview.js
@@ -12,11 +12,11 @@ function __rkward_debounce(fun) {
 }
 
 document.onselectionchange = __rkward_debounce(function() {
-	__rkward_sendMessage("selChanged", { sel: document.getSelection().toString() });
+	__rkward_sendMessage("selChanged", {sel : document.getSelection().toString()});
 });
 
 document.onscroll = __rkward_debounce(function() {
-	__rkward_sendMessage("scroll", { x: window.scrollX, y: window.scrollY });
+	__rkward_sendMessage("scroll", {x : window.scrollX, y : window.scrollY});
 });
 
 function __rkward_sendMessage(msg, args) {
@@ -30,19 +30,21 @@ function __rkward_sendMessage(msg, args) {
 document.addEventListener('click', e => {
 	const origin = e.target.closest('a');
 	if (origin && origin.href.startswith('#')) {
-		__rkward_sendMessage("pageInternalNav", { url: origin.href });
+		__rkward_sendMessage("pageInternalNav", {url : origin.href});
 		e.preventDefault();
 	}
 });
 
 document.addEventListener('contextmenu', e => {
-	let params = { x: e.screenX, y: e.screenY };
+	let params = {x : e.screenX, y : e.screenY};
 	try {
 		params.url = e.target.closest('a').href;
-	} catch {}
+	} catch {
+	}
 	try {
 		params.src = e.target.closest('img').src;
-	} catch {}
+	} catch {
+	}
 	__rkward_sendMessage("contextMenu", params);
 	e.preventDefault();
 });


From null at kde.org  Thu Apr  9 21:32:56 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Thu,  9 Apr 2026 20:32:56 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Add_occasional_build_against_R_devel?=
Message-ID: <20260409203256.238301400029@lerwini.kde.org>

Git commit 8af723ef13980cc0391303b64eb29fd362efddf7 by Thomas Friedrichsmeier.
Committed on 09/04/2026 at 20:32.
Pushed by tfry into branch 'master'.

Add occasional build against R devel

M  +29   -0    .gitlab-ci.yml
M  +30   -27   rkward/autotests/core_test.cpp

https://invent.kde.org/education/rkward/-/commit/8af723ef13980cc0391303b64eb29fd362efddf7

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e994d096e..c485d349a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -77,3 +77,32 @@ check_warnings:
   script:
     - "if [[ $(cat build/make.error | grep warning -i) ]]; then cat build/make.error; exit 1; fi"
   allow_failure: true
+
+# R does *not* have strong promise of backwards compatibility, we encounter breakage quite frequently for new minor releases
+# Check occasionally, what kind of breakage has already happened
+build_r_devel:
+ extends:
+  - .ci_linux_base
+  - .suse_tumbleweed_qt6stable
+ rules:
+  # only run on the scheduled interval, or manual
+  - if: $R_DEVEL_PIPELINE == "1"
+    allow_failure: false
+  - when: manual
+    allow_failure: true
+ script:
+    # fetch and build R devel version
+    - mkdir -p $CI_PROJECT_DIR/rdir && cd $CI_PROJECT_DIR/rdir
+    - wget https://cran.r-project.org/src/base-prerelease/R-devel.tar.xz
+    - tar -xJf R-devel.tar.xz
+    - mkdir install
+    - mkdir build && cd build
+    - ../R-devel/configure --prefix=$CI_PROJECT_DIR/rdir/install --enable-R-shlib --with-x=no --with-recommended-packages=no
+    - make -j 9 && make install
+    - cd ../..
+    # build rkward, next
+    - git config --global --add safe.directory $CI_PROJECT_DIR
+    # no dlopen() loading, to get at better symbol information for the backend process from ASAN
+    - python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux/Qt6/Shared --extra-cmake-args=-DR_EXECUTABLE=$CI_PROJECT_DIR/rdir/install/bin/R --extra-cmake-args=-DDLOPEN_RLIB=0
+ after_script:
+    - ci-utilities/run-collect-crashes.py
diff --git a/rkward/autotests/core_test.cpp b/rkward/autotests/core_test.cpp
index 1840d587d..8e3b81d2d 100644
--- a/rkward/autotests/core_test.cpp
+++ b/rkward/autotests/core_test.cpp
@@ -60,11 +60,40 @@ void testLog(const char *fmt, ...) {
 	va_end(ap);
 }
 
+void listBackendLog() {
+	testLog("Listing (new) contents of /tmp/rkward.rbackend");
+	QByteArray output, oldoutput;
+	QFile f(QDir::tempPath() + u"/rkward.rbackend"_s);
+	if (f.open(QIODevice::ReadOnly)) {
+		output = f.readAll();
+		f.close();
+	}
+
+	QFile fl(QDir::tempPath() + u"/rkward.rbackend.listed"_s);
+	if (fl.open(QIODevice::ReadOnly)) {
+		oldoutput = fl.readAll();
+		fl.close();
+	}
+
+	if (fl.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
+		fl.write(output);
+		fl.close();
+	}
+
+	if (output.startsWith(oldoutput)) {
+		output = output.sliced(oldoutput.length());
+	}
+	testLog("%s", qPrintable(QString::fromLocal8Bit(output)));
+}
+
 void RKDebug(int, int level, const char *fmt, ...) {
 	va_list ap;
 	va_start(ap, fmt);
 	testLog(fmt, ap);
-	if (level >= DL_ERROR) QFAIL("error message during test (see above)");
+	if (level >= DL_ERROR) {
+		listBackendLog();
+		QFAIL("error message during test (see above)");
+	}
 	va_end(ap);
 }
 
@@ -128,32 +157,6 @@ class RKWardCoreTest : public QObject {
 		RInterface::issueCommand(new RCommand(QStringLiteral("rm(list=ls(all.names=TRUE))"), RCommand::User));
 	}
 
-	void listBackendLog() {
-		testLog("Listing (new) contents of /tmp/rkward.rbackend");
-		QByteArray output, oldoutput;
-		QFile f(QDir::tempPath() + u"/rkward.rbackend"_s);
-		if (f.open(QIODevice::ReadOnly)) {
-			output = f.readAll();
-			f.close();
-		}
-
-		QFile fl(QDir::tempPath() + u"/rkward.rbackend.listed"_s);
-		if (fl.open(QIODevice::ReadOnly)) {
-			oldoutput = fl.readAll();
-			fl.close();
-		}
-
-		if (fl.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
-			fl.write(output);
-			fl.close();
-		}
-
-		if (output.startsWith(oldoutput)) {
-			output = output.sliced(oldoutput.length());
-		}
-		testLog("%s", qPrintable(QString::fromLocal8Bit(output)));
-	}
-
 	void waitForBackendStarted() {
 		QElapsedTimer t;
 		t.start();

From noreply at launchpad.net  Fri Apr 10 05:23:19 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Fri, 10 Apr 2026 04:23:19 -0000
Subject: [recipe build #4028663] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177579499985.714995.8811532693381158961.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 6 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028663/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-063

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4028663
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Fri Apr 10 05:28:09 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Fri, 10 Apr 2026 04:28:09 -0000
Subject: [recipe build #4028682] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177579528968.714995.2944384515674544349.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 8 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4028682/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-069

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4028682
Your team RKWard Team is the requester of the build.


From null at kde.org  Fri Apr 10 06:36:11 2026
From: null at kde.org (Yuri Chornoivan)
Date: Fri, 10 Apr 2026 05:36:11 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_rkward/settings=3A_Fix_minor_typo?=
Message-ID: <20260410053611.4211D1400D17@lerwini.kde.org>

Git commit ef3384446d280bbdd6bf4a3e3cb2c8dd20eb30a2 by Yuri Chornoivan.
Committed on 10/04/2026 at 05:36.
Pushed by yurchor into branch 'master'.

Fix minor typo

M  +1    -1    rkward/settings/rksettingsmodulegeneral.cpp

https://invent.kde.org/education/rkward/-/commit/ef3384446d280bbdd6bf4a3e3cb2c8dd20eb30a2

diff --git a/rkward/settings/rksettingsmodulegeneral.cpp b/rkward/settings/rksettingsmodulegeneral.cpp
index fdac86167..144af82bc 100644
--- a/rkward/settings/rksettingsmodulegeneral.cpp
+++ b/rkward/settings/rksettingsmodulegeneral.cpp
@@ -91,7 +91,7 @@ class RKSettingsPageGeneral : public RKSettingsModuleWidget {
 		vbox->addWidget(group_box);
 		main_vbox->addWidget(group);
 
-		group = new QGroupBox(i18n("HTML rendering (setings take effect after restarting RKWard)"));
+		group = new QGroupBox(i18n("HTML rendering (settings take effect after restarting RKWard)"));
 		hlayout = new QHBoxLayout(group);
 		hlayout->addWidget(new QLabel(i18n("Engine:")));
 		auto dd = RKSettingsModuleGeneral::html_engine.makeDropDown(RKConfigBase::LabelList({{RKSettingsModuleGeneral::DefaultRenderingEngine, i18n("Platform default")},

From null at kde.org  Fri Apr 10 07:13:45 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 10 Apr 2026 06:13:45 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Typos?=
Message-ID: <20260410061345.9C74E14003CB@lerwini.kde.org>

Git commit 91c333947ce0fbaaf0e498feaa64cdf75f210929 by Thomas Friedrichsmeier.
Committed on 10/04/2026 at 06:13.
Pushed by tfry into branch 'master'.

Typos

M  +2    -2    INSTALL

https://invent.kde.org/education/rkward/-/commit/91c333947ce0fbaaf0e498feaa64cdf75f210929

diff --git a/INSTALL b/INSTALL
index 44c73378a..590aef058 100644
--- a/INSTALL
+++ b/INSTALL
@@ -47,12 +47,12 @@ In some cases you may want to set the following options:
 	By default, the backend code is loaded using dlopen() to avoid certain runtime conflicts, esp. in distributed AppImages. This, however, makes it hard to
 	get at proper debug symbol information. For debugging purposes it may thus be useful to set this to "0", explictly
 -DFORCE_WITH_QWEBENGINE=ON
+-DFORCE_WITH_QWEBVIEW=ON
 	RKWard supports two HTML rendering libraries, QWebEngine or QWebView. By default, either - or both - will be selected automatically, based on the
-	presence of the required libaries. Use this (and/or -DFORCE_WOTH_QWEBVIEW) to override the automatic selction. Setting either öf these options to ON
+	presence of the required libaries. Use these options to override the automatic selection. Setting either of these options to ON
 	will disable building against the other library *unless* that is also specified with FORCE_WITH_XYZ.
 	Packagers please note that, on Linux, using QWebView currently still requires a runtime dependency on QWebEngine, as no native rendering plugin is
 	available in Qt at the time of this writing.
--DFORCE_WITH_QWEBVIEW=ON
 
 Further generic options are listed on http://www.cmake.org/Wiki/CMake_Useful_Variables .
 

From null at kde.org  Fri Apr 10 19:00:52 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 10 Apr 2026 18:00:52 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Reenable_check_for_passing_tests_on_Windows?=
Message-ID: <20260410180052.609D41400576@lerwini.kde.org>

Git commit afcb4e418962480ffc73694a1da0ef3dc8da4aa1 by Thomas Friedrichsmeier.
Committed on 10/04/2026 at 18:00.
Pushed by tfry into branch 'master'.

Reenable check for passing tests on Windows

M  +1    -1    .kde-ci.yml

https://invent.kde.org/education/rkward/-/commit/afcb4e418962480ffc73694a1da0ef3dc8da4aa1

diff --git a/.kde-ci.yml b/.kde-ci.yml
index 61f6d6b0a..b44bba61b 100644
--- a/.kde-ci.yml
+++ b/.kde-ci.yml
@@ -19,7 +19,7 @@ Dependencies:
     'frameworks/kcrash': '@latest-kf6'
 
 Options:
-  require-passing-tests-on: [ 'Linux', 'FreeBSD' ] # TODO test failure temporarily allowed on Windows (QWebEngine crash)
+  require-passing-tests-on: [ 'Linux', 'FreeBSD', 'Windows' ]
   tests-load-sensitve: True
   per-test-timeout: 180
   cppcheck-arguments: '--enable=warning,style,performance --inline-suppr -DR_EXECUTABLE="/usr/bin/R" -DRKWARD_VERSION="dummy" -DCPPCHECK_ONLY=1 --suppress=unknownMacro --suppress=preprocessorErrorDirective --suppress=returnByReference'

From null at kde.org  Fri Apr 10 19:19:54 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 10 Apr 2026 18:19:54 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Bookkeeping?=
Message-ID: <20260410181954.9A9E3140253A@lerwini.kde.org>

Git commit 9ef8f33d4119804e89a9584c6bc9609426e5ffeb by Thomas Friedrichsmeier.
Committed on 10/04/2026 at 18:19.
Pushed by tfry into branch 'master'.

Bookkeeping

M  +4    -0    ChangeLog

https://invent.kde.org/education/rkward/-/commit/9ef8f33d4119804e89a9584c6bc9609426e5ffeb

diff --git a/ChangeLog b/ChangeLog
index 8e45e5b73..0367a56a8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,8 @@
 --- Version 0.8.3 - UNRELEASED
+- Fixed: Remove obsolete dep# on dbus
+- Added: Allow to define custom script preview modes (rk.register.script.preview()))
+- Added: Various small updates to the R highlighting in the R console
+- Added: Allow translation of strings in the rkward R package
 - Added: Support HTML rendering with QWebView, which allows to use native renderers on some platforms
 - Fixed: Crash when splitting view on a data.frame editor window
 - Fixed: Allow compilation with the upcoming R 4.6

From noreply at launchpad.net  Sat Apr 11 05:24:44 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 11 Apr 2026 04:24:44 -0000
Subject: [recipe build #4029001] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177588148431.1895148.6566215104508357294.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 8 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4029001/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-059

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4029001
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sat Apr 11 05:29:09 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 11 Apr 2026 04:29:09 -0000
Subject: [recipe build #4029014] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177588174923.1895148.15112448661061024761.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 7 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4029014/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-070

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4029014
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Mon Apr 13 05:20:26 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Mon, 13 Apr 2026 04:20:26 -0000
Subject: [recipe build #4029682] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177605402646.1895148.5768104566087081909.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 3 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4029682/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-013

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4029682
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Mon Apr 13 05:30:24 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Mon, 13 Apr 2026 04:30:24 -0000
Subject: [recipe build #4029702] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177605462460.1895148.14594120003243019713.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 5 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4029702/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-062

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4029702
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Tue Apr 14 05:20:08 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Tue, 14 Apr 2026 04:20:08 -0000
Subject: [recipe build #4030079] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177614040810.3282238.14493941126410810237.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 5 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4030079/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-051

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4030079
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Tue Apr 14 05:20:08 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Tue, 14 Apr 2026 04:20:08 -0000
Subject: [recipe build #4030099] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177614040853.3282238.1000582705965056107.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4030099/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-081

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4030099
Your team RKWard Team is the requester of the build.


From null at kde.org  Tue Apr 14 07:24:50 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Tue, 14 Apr 2026 06:24:50 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_/=3A_Prepare_release?=
Message-ID: <20260414062450.C845F1400E4C@lerwini.kde.org>

Git commit da6aafc5b664fd3d80633b8bc8515483c0276d09 by Thomas Friedrichsmeier.
Committed on 14/04/2026 at 06:23.
Pushed by tfry into branch 'releases/0.8.3'.

Prepare release

M  +1    -1    ChangeLog
M  +1    -1    VERSION.cmake
M  +1    -1    rkward/plugins/pluginmap_meta.inc
M  +1    -1    rkward/rbackend/rpackages/rkward/DESCRIPTION

https://invent.kde.org/education/rkward/-/commit/da6aafc5b664fd3d80633b8bc8515483c0276d09

diff --git a/ChangeLog b/ChangeLog
index 0367a56a8..e0d767284 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,4 +1,4 @@
---- Version 0.8.3 - UNRELEASED
+--- Version 0.8.3 - Apr-24-2026
 - Fixed: Remove obsolete dep# on dbus
 - Added: Allow to define custom script preview modes (rk.register.script.preview()))
 - Added: Various small updates to the R highlighting in the R console
diff --git a/VERSION.cmake b/VERSION.cmake
index f6099e76e..d1308a1db 100644
--- a/VERSION.cmake
+++ b/VERSION.cmake
@@ -1,3 +1,3 @@
 # DO NOT CHANGE THIS FILE MANUALLY!
 # It will be overwritten by scripts/set_dist_version.sh
-SET(RKVERSION_NUMBER 0.8.2z+0.8.3+devel4)
+SET(RKVERSION_NUMBER 0.8.3)
diff --git a/rkward/plugins/pluginmap_meta.inc b/rkward/plugins/pluginmap_meta.inc
index ec934adb6..0846c574e 100644
--- a/rkward/plugins/pluginmap_meta.inc
+++ b/rkward/plugins/pluginmap_meta.inc
@@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		</snippet>
 		<snippet id="rkward_version_guard">
 			<!-- Guard against accidental inclusion of pluginmaps in a different installed version of RKWard -->
-			<dependencies rkward_min_version="0.8.2z" rkward_max_version="0.8.3y" />
+			<dependencies rkward_min_version="0.8.3" rkward_max_version="0.8.3y" />
 		</snippet>
 	</snippets>
 </document>
diff --git a/rkward/rbackend/rpackages/rkward/DESCRIPTION b/rkward/rbackend/rpackages/rkward/DESCRIPTION
index 686792624..1c49d9ec9 100755
--- a/rkward/rbackend/rpackages/rkward/DESCRIPTION
+++ b/rkward/rbackend/rpackages/rkward/DESCRIPTION
@@ -15,7 +15,7 @@ LazyLoad: yes
 Authors at R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier at kdemail.net", role=c("aut")), person(given="the RKWard", family="team",
                     email="rkward at kde.org", role=c("cre","aut")))
 Version: 0.8.3
-Date: 2025-12-13
+Date: 2026-04-14
 RoxygenNote: 7.3.3
 Collate: 
     'base_overrides.R'

From null at kde.org  Tue Apr 14 07:27:35 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Tue, 14 Apr 2026 06:27:35 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Bump_version_in_master?=
Message-ID: <20260414062735.D174C1400E4C@lerwini.kde.org>

Git commit 2d0fcafee08c89dd2ddf1a813d83b4a46f152c51 by Thomas Friedrichsmeier.
Committed on 14/04/2026 at 06:27.
Pushed by tfry into branch 'master'.

Bump version in master

M  +3    -0    ChangeLog
M  +1    -1    VERSION.cmake
M  +1    -1    rkward/plugins/analysis.pluginmap
M  +1    -1    rkward/plugins/data.pluginmap
M  +1    -1    rkward/plugins/distributions.pluginmap
M  +1    -1    rkward/plugins/embedded.pluginmap
M  +1    -1    rkward/plugins/import_export.pluginmap
M  +1    -1    rkward/plugins/irt.pluginmap
M  +1    -1    rkward/plugins/menu.pluginmap
M  +1    -1    rkward/plugins/plots.pluginmap
M  +1    -1    rkward/plugins/pluginmap_meta.inc
M  +1    -1    rkward/plugins/under_development.pluginmap
M  +1    -1    rkward/plugins/x11device.pluginmap
M  +1    -1    rkward/rbackend/rpackages/rkward/DESCRIPTION

https://invent.kde.org/education/rkward/-/commit/2d0fcafee08c89dd2ddf1a813d83b4a46f152c51

diff --git a/ChangeLog b/ChangeLog
index e0d767284..b27b93c52 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+--- Version 0.8.4 - UNRELEASED
+
+
 --- Version 0.8.3 - Apr-24-2026
 - Fixed: Remove obsolete dep# on dbus
 - Added: Allow to define custom script preview modes (rk.register.script.preview()))
diff --git a/VERSION.cmake b/VERSION.cmake
index d1308a1db..1ab50e541 100644
--- a/VERSION.cmake
+++ b/VERSION.cmake
@@ -1,3 +1,3 @@
 # DO NOT CHANGE THIS FILE MANUALLY!
 # It will be overwritten by scripts/set_dist_version.sh
-SET(RKVERSION_NUMBER 0.8.3)
+SET(RKVERSION_NUMBER 0.8.3z+0.8.4+devel1)
diff --git a/rkward/plugins/analysis.pluginmap b/rkward/plugins/analysis.pluginmap
index 479dbf718..d07f07690 100644
--- a/rkward/plugins/analysis.pluginmap
+++ b/rkward/plugins/analysis.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Analysis"
 		shortinfo="Assorted plugins for univariate and multivariate data analysis. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/data.pluginmap b/rkward/plugins/data.pluginmap
index 417f5ef83..866563e21 100644
--- a/rkward/plugins/data.pluginmap
+++ b/rkward/plugins/data.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Data transformation and conversion"
 		shortinfo="Plugins for transforming, subsetting, recoding, etc. of data. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/distributions.pluginmap b/rkward/plugins/distributions.pluginmap
index 602199e59..1d76ed004 100644
--- a/rkward/plugins/distributions.pluginmap
+++ b/rkward/plugins/distributions.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Distributions"
 		shortinfo="Distribution functions, plots, tests of (normal) distribution, central limit theorem. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/embedded.pluginmap b/rkward/plugins/embedded.pluginmap
index b629eba8f..ea895ec43 100644
--- a/rkward/plugins/embedded.pluginmap
+++ b/rkward/plugins/embedded.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Embedded plugins"
 		shortinfo="Not for direct use: Utility plugins that can be included by other plugins. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/import_export.pluginmap b/rkward/plugins/import_export.pluginmap
index c475e8641..0a5d7e6ea 100644
--- a/rkward/plugins/import_export.pluginmap
+++ b/rkward/plugins/import_export.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Data Import and Export"
 		shortinfo="Plugins for reading and writing data in various formats. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/irt.pluginmap b/rkward/plugins/irt.pluginmap
index 558d9d82e..c8da62a38 100644
--- a/rkward/plugins/irt.pluginmap
+++ b/rkward/plugins/irt.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Item Response Theory"
 		shortinfo="Item Response Theory Plugins. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/menu.pluginmap b/rkward/plugins/menu.pluginmap
index e12d7d86b..d54e8e72c 100644
--- a/rkward/plugins/menu.pluginmap
+++ b/rkward/plugins/menu.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Menu structure definition"
 		shortinfo="Not for direct use: Defines the names and order of top level menus. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/plots.pluginmap b/rkward/plugins/plots.pluginmap
index c742e813e..52959622e 100644
--- a/rkward/plugins/plots.pluginmap
+++ b/rkward/plugins/plots.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Plots"
 		shortinfo="Plugins for producing various plots. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/pluginmap_meta.inc b/rkward/plugins/pluginmap_meta.inc
index 0846c574e..f47c956e6 100644
--- a/rkward/plugins/pluginmap_meta.inc
+++ b/rkward/plugins/pluginmap_meta.inc
@@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 		</snippet>
 		<snippet id="rkward_version_guard">
 			<!-- Guard against accidental inclusion of pluginmaps in a different installed version of RKWard -->
-			<dependencies rkward_min_version="0.8.3" rkward_max_version="0.8.3y" />
+			<dependencies rkward_min_version="0.8.3z" rkward_max_version="0.8.4y" />
 		</snippet>
 	</snippets>
 </document>
diff --git a/rkward/plugins/under_development.pluginmap b/rkward/plugins/under_development.pluginmap
index 0a0854897..93145b0ec 100644
--- a/rkward/plugins/under_development.pluginmap
+++ b/rkward/plugins/under_development.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Plugins under development"
 		shortinfo="Plugins that are under development, experimental, or meant for testing, only. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/plugins/x11device.pluginmap b/rkward/plugins/x11device.pluginmap
index ea1e0eca9..a09a97321 100644
--- a/rkward/plugins/x11device.pluginmap
+++ b/rkward/plugins/x11device.pluginmap
@@ -10,7 +10,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
 	<about
 		name="Graphics Device add-ons"
 		shortinfo="Plugins for use in graphics device (e.g. X11()) windows. Part of the official RKWard distribution"
-		version="0.8.3"
+		version="0.8.4"
 		license="GPL"
 		url="https://rkward.kde.org">
 		<insert snippet="author_rkward_team"/>
diff --git a/rkward/rbackend/rpackages/rkward/DESCRIPTION b/rkward/rbackend/rpackages/rkward/DESCRIPTION
index 1c49d9ec9..4e4fddb49 100755
--- a/rkward/rbackend/rpackages/rkward/DESCRIPTION
+++ b/rkward/rbackend/rpackages/rkward/DESCRIPTION
@@ -14,7 +14,7 @@ Encoding: UTF-8
 LazyLoad: yes
 Authors at R: c(person(given="Thomas", family="Friedrichsmeier", email="thomas.friedrichsmeier at kdemail.net", role=c("aut")), person(given="the RKWard", family="team",
                     email="rkward at kde.org", role=c("cre","aut")))
-Version: 0.8.3
+Version: 0.8.4
 Date: 2026-04-14
 RoxygenNote: 7.3.3
 Collate: 

From noreply at launchpad.net  Wed Apr 15 10:35:07 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Wed, 15 Apr 2026 09:35:07 -0000
Subject: [recipe build #4030446] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177624570708.3282238.6868659160190339171.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4030446/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-077

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4030446
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Wed Apr 15 10:38:21 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Wed, 15 Apr 2026 09:38:21 -0000
Subject: [recipe build #4030459] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177624590147.3282238.5929740590375348358.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4030459/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-069

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4030459
Your team RKWard Team is the requester of the build.


From null at kde.org  Fri Apr 17 12:34:33 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 11:34:33 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Fix_build_with_R_4=2E6?=
Message-ID: <20260417113433.70E9C1400634@lerwini.kde.org>

Git commit 29dceccd333ca2ed55b23b26375d6fa57dbc9758 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 11:34.
Pushed by tfry into branch 'releases/0.8.3'.

Fix build with R 4.6

M  +4    -0    rkward/rbackend/rkrapi.h

https://invent.kde.org/education/rkward/-/commit/29dceccd333ca2ed55b23b26375d6fa57dbc9758

diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 6296f2d60..552d693f7 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -91,6 +91,10 @@ available as a config option.
 
 #include <Rversion.h>
 
+#if (R_VERSION >= R_Version(4, 5, 9))
+#	define ENABLE_LEGACY_NONAPI 1
+#endif
+
 #if defined(Win32) && (R_VERSION >= R_Version(4, 3, 0))
 // We don't use Complex.h ourselves, but it is included from Rinternals.h and comes with a non-standard definition
 // that trips up MSVC. Fortunately, we can disable that via a define.

From null at kde.org  Fri Apr 17 16:32:41 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 15:32:41 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Cope_with_removal_of_FORMALS_in_R_4=2E6?=
Message-ID: <20260417153241.DBC421400907@lerwini.kde.org>

Git commit ee0214fb20fc701ba54d151d8cdcdfe5ddd7caf9 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 13:35.
Pushed by tfry into branch 'releases/0.8.3'.

Cope with removal of FORMALS in R 4.6

M  +7    -0    rkward/rbackend/rkrapi.cpp
M  +6    -1    rkward/rbackend/rkrapi.h
M  +2    -2    rkward/rbackend/rkstructuregetter.cpp

https://invent.kde.org/education/rkward/-/commit/ee0214fb20fc701ba54d151d8cdcdfe5ddd7caf9

diff --git a/rkward/rbackend/rkrapi.cpp b/rkward/rbackend/rkrapi.cpp
index 46b8626b0..979b2b0ad 100644
--- a/rkward/rbackend/rkrapi.cpp
+++ b/rkward/rbackend/rkrapi.cpp
@@ -30,4 +30,11 @@ void RFn::init(void *libr_dll_handle, void *(*dlsym_fun)(void *, const char *))
 #else
 	RK_DEBUG(RBACKEND, DL_DEBUG, "R lib already linked");
 #endif
+
+	// work around various incompatiblities between R versions
+	// R < 4.5.0
+	if (!R_ClosureFormals) {
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
+		R_ClosureFormals = reinterpret_cast<decltype(R_ClosureFormals)>(dlsym_fun(libr_dll_handle, "FORMALS"));
+	}
 }
diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 552d693f7..f8b8831c8 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -174,7 +174,12 @@ class RFn : public QObject {
 	IMPORT_R_API(GEgetDevice);
 	IMPORT_R_API(GEplayDisplayList);
 	IMPORT_R_API(R_CHAR);
-	IMPORT_R_API(FORMALS);
+#if (R_VERSION >= R_Version(4, 5, 0))
+	IMPORT_R_API(R_ClosureFormals);
+#else
+	// will be handled, manually, in init
+	static SEXP (*R_ClosureFormals)(SEXP);
+#endif
 	IMPORT_R_API(INTEGER);
 	IMPORT_R_API(LOGICAL);
 	IMPORT_R_API(LENGTH);
diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index d2d1840b2..2fcd18f77 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -434,9 +434,9 @@ void RKStructureGetter::getStructureWorker(SEXP val, const QString &name, int ad
 		if (RFn::Rf_isPrimitive(value)) {
 			// primitives don't have formals, internally
 			auto args = RKRSupport::callSimpleFun(args_fun, value, baseenv);
-			if (!RFn::Rf_isNull(args)) formals_s = RFn::FORMALS(args);
+			if (!RFn::Rf_isNull(args)) formals_s = RFn::R_ClosureFormals(args);
 		} else {
-			formals_s = RFn::FORMALS(value);
+			formals_s = RFn::R_ClosureFormals(value);
 		}
 		RFn::Rf_protect(formals_s);
 


From null at kde.org  Fri Apr 17 16:32:41 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 15:32:41 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Make_do_without_SET=5FPRx_functions?=
Message-ID: <20260417153241.DE9C61401398@lerwini.kde.org>

Git commit 63bf8a9bae8588b87f55af7eaea73f9c4410e782 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 13:37.
Pushed by tfry into branch 'releases/0.8.3'.

Make do without SET_PRx functions

M  +0    -2    rkward/rbackend/rkrapi.h
M  +5    -8    rkward/rbackend/rkstructuregetter.cpp
M  +1    -1    rkward/rbackend/rkstructuregetter.h

https://invent.kde.org/education/rkward/-/commit/63bf8a9bae8588b87f55af7eaea73f9c4410e782

diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index f8b8831c8..c3b144934 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -297,8 +297,6 @@ class RFn : public QObject {
 	IMPORT_R_API(STRING_ELT);
 	IMPORT_R_API(SET_STRING_ELT);
 	IMPORT_R_API(SET_VECTOR_ELT);
-	IMPORT_R_API(SET_PRENV);
-	IMPORT_R_API(SET_PRVALUE);
 	IMPORT_R_API(TYPEOF);
 	IMPORT_R_API(VECTOR_ELT);
 	IMPORT_R_API(run_Rmainloop);
diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index 2fcd18f77..d1f079431 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -130,11 +130,14 @@ void RKStructureGetter::getStructureSafe(SEXP value, const QString &name, int ad
  *
  *  @note This is is not quite perfect, however. E.g. if we have two promises a and b, where b takes a slice out of a, then
  *        evaluating b will force a, permanently. */
-SEXP RKStructureGetter::resolvePromise(SEXP from) {
+SEXP RKStructureGetter::resolvePromise(SEXP from, SEXP env) {
 	RK_TRACE(RBACKEND);
 
 	SEXP ret = from;
 	if (RFn::TYPEOF(from) == PROMSXP) {
+		if (keep_evalled_promises) {
+			return RFn::Rf_eval(ret, env);
+		}
 		ret = RFn::PRVALUE(from);
 		if (ret == ROb(R_UnboundValue)) {
 			RK_DEBUG(RBACKEND, DL_TRACE, "temporarily resolving unbound promise");
@@ -144,10 +147,6 @@ SEXP RKStructureGetter::resolvePromise(SEXP from) {
 			//        Not setting it from here, only means, any recursion will be detected one level later.
 			ret = RFn::Rf_eval(RFn::PRCODE(from), RFn::PRENV(from));
 			// SET_PRSEEN(from, 0);
-			if (keep_evalled_promises) {
-				RFn::SET_PRVALUE(from, ret);
-				RFn::SET_PRENV(from, ROb(R_NilValue));
-			}
 			RFn::Rf_unprotect(1);
 
 			RK_DEBUG(RBACKEND, DL_TRACE, "resolved type is %d", RFn::TYPEOF(ret));
@@ -174,8 +173,6 @@ void RKStructureGetter::getStructureWorker(SEXP val, const QString &name, int ad
 	SEXP value = val;
 	PROTECT_INDEX value_index;
 	RFn::R_ProtectWithIndex(value, &value_index);
-	// manually resolve any promises
-	RFn::R_Reprotect(value = resolvePromise(value), value_index);
 
 	bool is_s4 = RFn::Rf_isS4(value);
 	SEXP baseenv = ROb(R_BaseEnv);
@@ -372,7 +369,7 @@ void RKStructureGetter::getStructureWorker(SEXP val, const QString &name, int ad
 			for (int i = 0; i < childcount; ++i) {
 				SEXP current_childname = RFn::Rf_install(RFn::R_CHAR(RFn::STRING_ELT(childnames_s, i))); // ??? Why does simply using RFn::STRING_ELT(childnames_i, i) crash?
 				RFn::Rf_protect(current_childname);
-				SEXP child = RFn::Rf_findVar(current_childname, value);
+				SEXP child = resolvePromise(RFn::Rf_findVar(current_childname, value), value);
 				RFn::Rf_protect(child);
 
 				getStructureSafe(child, childnames[i], 0, children[i], nesting_depth + 1);
diff --git a/rkward/rbackend/rkstructuregetter.h b/rkward/rbackend/rkstructuregetter.h
index 4951eee17..9b92bffa8 100644
--- a/rkward/rbackend/rkstructuregetter.h
+++ b/rkward/rbackend/rkstructuregetter.h
@@ -36,7 +36,7 @@ class RKStructureGetter {
 	/** needed to wrap things inside an R_ToplevelExec */
 	static void getStructureWrapper(GetStructureWorkerArgs *data);
 	void getStructureSafe(SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth);
-	SEXP resolvePromise(SEXP from);
+	SEXP resolvePromise(SEXP from, SEXP env);
 
 	SEXP prefetch_fun(const char *name, bool from_base = true);
 


From null at kde.org  Fri Apr 17 16:32:41 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 15:32:41 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Adapt_to_removal/rename_of_promise-handling_functions?=
Message-ID: <20260417153241.DFAA114013ED@lerwini.kde.org>

Git commit 9dbaa5068608e9e668491c32d80e86ebd931e905 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 15:31.
Pushed by tfry into branch 'releases/0.8.3'.

Adapt to removal/rename of promise-handling functions

M  +10   -1    rkward/rbackend/rkrapi.cpp
M  +20   -6    rkward/rbackend/rkrapi.h
M  +1    -2    rkward/rbackend/rkrbackend.cpp
M  +27   -6    rkward/rbackend/rkstructuregetter.cpp
M  +1    -1    rkward/rbackend/rkstructuregetter.h

https://invent.kde.org/education/rkward/-/commit/9dbaa5068608e9e668491c32d80e86ebd931e905

diff --git a/rkward/rbackend/rkrapi.cpp b/rkward/rbackend/rkrapi.cpp
index 979b2b0ad..eb56a1c05 100644
--- a/rkward/rbackend/rkrapi.cpp
+++ b/rkward/rbackend/rkrapi.cpp
@@ -34,7 +34,16 @@ void RFn::init(void *libr_dll_handle, void *(*dlsym_fun)(void *, const char *))
 	// work around various incompatiblities between R versions
 	// R < 4.5.0
 	if (!R_ClosureFormals) {
-		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
 		R_ClosureFormals = reinterpret_cast<decltype(R_ClosureFormals)>(dlsym_fun(libr_dll_handle, "FORMALS"));
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
 	}
+#if R_VERSION != R_Version(4, 6, 0)
+	if (!R_GetBindingType) {
+		PRCODE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRCODE"));
+		PRENV = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRENV"));
+		PRVALUE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRVALUE"));
+		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_GetBindingType to PRCODE (%p)", PRCODE);
+		RK_ASSERT(PRCODE);
+	}
+#endif
 }
diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index c3b144934..8e7ba33fe 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -177,16 +177,30 @@ class RFn : public QObject {
 #if (R_VERSION >= R_Version(4, 5, 0))
 	IMPORT_R_API(R_ClosureFormals);
 #else
-	// will be handled, manually, in init
-	static SEXP (*R_ClosureFormals)(SEXP);
+	// used to be named FORMALS; will be handled in init, manually
+	static inline SEXP (*R_ClosureFormals)(SEXP);
+#endif
+#if (R_VERSION >= R_Version(4, 6, 0))
+	IMPORT_R_API(R_GetBindingType);
+	//IMPORT_R_API(R_ActiveBindingFunction);
+	IMPORT_R_API(R_DelayedBindingExpression);
+	IMPORT_R_API(R_DelayedBindingEnvironment);
+	//IMPORT_R_API(R_ForcedBindingExpression);
+	IMPORT_R_API(R_getVar);
+	// these were removed in R 4.6, but we still need them
+	// if using a lower R at runtime
+	static inline SEXP (*PRCODE)(SEXP) = nullptr;
+	static inline SEXP (*PRENV)(SEXP) = nullptr;
+	static inline SEXP (*PRVALUE)(SEXP) = nullptr;
+#else
+	IMPORT_R_API(PRCODE);
+	IMPORT_R_API(PRENV);
+	IMPORT_R_API(PRVALUE);
 #endif
 	IMPORT_R_API(INTEGER);
 	IMPORT_R_API(LOGICAL);
 	IMPORT_R_API(LENGTH);
-	IMPORT_R_API(PRCODE);
-	IMPORT_R_API(PRENV);
 	IMPORT_R_API(PRINTNAME);
-	IMPORT_R_API(PRVALUE);
 	IMPORT_R_API(REAL);
 	IMPORT_R_API(R_CheckDeviceAvailable);
 	IMPORT_R_API(R_CheckStack);
@@ -293,7 +307,7 @@ class RFn : public QObject {
 	IMPORT_R_API(Riconv_close);
 	IMPORT_R_API(Riconv_open);
 	IMPORT_R_API(SET_TAG);
-	IMPORT_R_API(SET_TYPEOF);
+	IMPORT_R_API(Rf_lcons);
 	IMPORT_R_API(STRING_ELT);
 	IMPORT_R_API(SET_STRING_ELT);
 	IMPORT_R_API(SET_VECTOR_ELT);
diff --git a/rkward/rbackend/rkrbackend.cpp b/rkward/rbackend/rkrbackend.cpp
index e69ec744c..190ad9f58 100644
--- a/rkward/rbackend/rkrbackend.cpp
+++ b/rkward/rbackend/rkrbackend.cpp
@@ -1207,8 +1207,7 @@ RCommandProxy *RKRBackend::runDirectCommand(const QString &command, RCommand::Co
 
 static void setWarnOption(int level, bool tryeval = false) {
 	SEXP s, t;
-	RFn::Rf_protect(t = s = RFn::Rf_allocList(2));
-	RFn::SET_TYPEOF(s, LANGSXP);
+	RFn::Rf_protect(t = s = RFn::Rf_lcons(ROb(R_NilValue), RFn::Rf_allocList(2)));
 	RFn::SETCAR(t, RFn::Rf_install("options"));
 	t = RFn::CDR(t);
 	RFn::SETCAR(t, RFn::Rf_ScalarInteger(level));
diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index d1f079431..a44161c62 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -125,19 +125,40 @@ void RKStructureGetter::getStructureSafe(SEXP value, const QString &name, int ad
 	}
 }
 
-/** Temporarily resolve a promise, usually without keeping its value (unless keep_evalled_promises is set, which it never is, at the time of this writing).
+/** Get a symbol value from an environment, taking care not to (permanently) for promises (unless keep_evalled_promises is set,
+ *  which it never is, at the time of this writing).
  *  This is useful for peeking into large objects while building the object tree, without permanently using lots of RAM.
  *
  *  @note This is is not quite perfect, however. E.g. if we have two promises a and b, where b takes a slice out of a, then
  *        evaluating b will force a, permanently. */
-SEXP RKStructureGetter::resolvePromise(SEXP from, SEXP env) {
+SEXP RKStructureGetter::peekFromEnv(SEXP sym, SEXP env) {
 	RK_TRACE(RBACKEND);
 
+	if (keep_evalled_promises) {
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+#if R_VERSION >= R_Version(4, 6, 0)
+	if (RFn::R_GetBindingType) { // implies R runtime >= 4.6
+		const auto type = RFn::R_GetBindingType(sym, env);
+		if (type == R_BindingTypeDelayed) {
+			RK_DEBUG(RBACKEND, DL_TRACE, "temporarily resolving delayed expression");
+			return RFn::Rf_eval(RFn::R_DelayedBindingExpression(sym, env), RFn::R_DelayedBindingEnvironment(sym, env));
+		} else if (type == R_BindingTypeMissing) {
+			return ROb(R_NilValue);
+		} else if (type == R_BindingTypeUnbound) {
+			return ROb(R_NilValue);
+		}
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+#endif
+	if (!RFn::PRCODE) {
+		// We have neither the new nor the old promise handling functions?
+		// fall back to forcing
+		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+	}
+	SEXP from = RFn::Rf_findVar(sym, env);
 	SEXP ret = from;
 	if (RFn::TYPEOF(from) == PROMSXP) {
-		if (keep_evalled_promises) {
-			return RFn::Rf_eval(ret, env);
-		}
 		ret = RFn::PRVALUE(from);
 		if (ret == ROb(R_UnboundValue)) {
 			RK_DEBUG(RBACKEND, DL_TRACE, "temporarily resolving unbound promise");
@@ -369,7 +390,7 @@ void RKStructureGetter::getStructureWorker(SEXP val, const QString &name, int ad
 			for (int i = 0; i < childcount; ++i) {
 				SEXP current_childname = RFn::Rf_install(RFn::R_CHAR(RFn::STRING_ELT(childnames_s, i))); // ??? Why does simply using RFn::STRING_ELT(childnames_i, i) crash?
 				RFn::Rf_protect(current_childname);
-				SEXP child = resolvePromise(RFn::Rf_findVar(current_childname, value), value);
+				SEXP child = peekFromEnv(current_childname, value);
 				RFn::Rf_protect(child);
 
 				getStructureSafe(child, childnames[i], 0, children[i], nesting_depth + 1);
diff --git a/rkward/rbackend/rkstructuregetter.h b/rkward/rbackend/rkstructuregetter.h
index 9b92bffa8..6dafc92a4 100644
--- a/rkward/rbackend/rkstructuregetter.h
+++ b/rkward/rbackend/rkstructuregetter.h
@@ -36,7 +36,7 @@ class RKStructureGetter {
 	/** needed to wrap things inside an R_ToplevelExec */
 	static void getStructureWrapper(GetStructureWorkerArgs *data);
 	void getStructureSafe(SEXP value, const QString &name, int add_type_flags, RData *storage, int nesting_depth);
-	SEXP resolvePromise(SEXP from, SEXP env);
+	SEXP peekFromEnv(SEXP symb, SEXP env);
 
 	SEXP prefetch_fun(const char *name, bool from_base = true);
 

From null at kde.org  Fri Apr 17 16:43:41 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 15:43:41 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Fix_compilation_with_R_=3C_4=2E6=2C_now?=
Message-ID: <20260417154341.8BAD21400224@lerwini.kde.org>

Git commit 6cc8ac06fcb498f13c9819e9f7294a97874f123e by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 15:43.
Pushed by tfry into branch 'releases/0.8.3'.

Fix compilation with R < 4.6, now

M  +1    -1    rkward/rbackend/rkrapi.h

https://invent.kde.org/education/rkward/-/commit/6cc8ac06fcb498f13c9819e9f7294a97874f123e

diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 8e7ba33fe..06c9ca365 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -186,7 +186,6 @@ class RFn : public QObject {
 	IMPORT_R_API(R_DelayedBindingExpression);
 	IMPORT_R_API(R_DelayedBindingEnvironment);
 	//IMPORT_R_API(R_ForcedBindingExpression);
-	IMPORT_R_API(R_getVar);
 	// these were removed in R 4.6, but we still need them
 	// if using a lower R at runtime
 	static inline SEXP (*PRCODE)(SEXP) = nullptr;
@@ -276,6 +275,7 @@ class RFn : public QObject {
 	IMPORT_R_API(Rf_findVarInFrame);
 	IMPORT_R_API(Rf_getAttrib);
 	IMPORT_R_API(Rf_getCharCE);
+	IMPORT_R_API(R_getVar);
 	IMPORT_R_API(Rf_initialize_R);
 	IMPORT_R_API(Rf_install);
 	IMPORT_R_API(Rf_installChar);

From null at kde.org  Fri Apr 17 17:04:36 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 16:04:36 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Fix_compilation_with_regular_linkage?=
Message-ID: <20260417160436.CD5BE14002E5@lerwini.kde.org>

Git commit 09a117bb5a85c313abbd0ad4b5b61d0554e6b2d2 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 16:04.
Pushed by tfry into branch 'releases/0.8.3'.

Fix compilation with regular linkage

M  +9    -8    rkward/rbackend/rkrapi.cpp

https://invent.kde.org/education/rkward/-/commit/09a117bb5a85c313abbd0ad4b5b61d0554e6b2d2

diff --git a/rkward/rbackend/rkrapi.cpp b/rkward/rbackend/rkrapi.cpp
index eb56a1c05..8e879da0f 100644
--- a/rkward/rbackend/rkrapi.cpp
+++ b/rkward/rbackend/rkrapi.cpp
@@ -27,23 +27,24 @@ void RFn::init(void *libr_dll_handle, void *(*dlsym_fun)(void *, const char *))
 		RK_DEBUG(RBACKEND, DL_DEBUG, "Lookup of symbol %s in %p: %p", name, libr_dll_handle, symb);
 		prop.write(rfn, QVariant::fromValue((void *)symb)); // NOTE: Qt refuses to write nullptr as value, but that's already the initial value of each member
 	}
-#else
-	RK_DEBUG(RBACKEND, DL_DEBUG, "R lib already linked");
-#endif
 
 	// work around various incompatiblities between R versions
 	// R < 4.5.0
 	if (!R_ClosureFormals) {
-		R_ClosureFormals = reinterpret_cast<decltype(R_ClosureFormals)>(dlsym_fun(libr_dll_handle, "FORMALS"));
+		*(void **)(&R_ClosureFormals) = dlsym_fun(libr_dll_handle, "FORMALS");
 		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_ClosureFormals to FORMALS (%p)", R_ClosureFormals);
 	}
-#if R_VERSION != R_Version(4, 6, 0)
+#	if R_VERSION >= R_Version(4, 6, 0)
 	if (!R_GetBindingType) {
-		PRCODE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRCODE"));
-		PRENV = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRENV"));
-		PRVALUE = reinterpret_cast<decltype(PRCODE)>(dlsym_fun(libr_dll_handle, "PRVALUE"));
+		*(void **)(&PRCODE) = dlsym_fun(libr_dll_handle, "PRCODE");
+		*(void **)(&PRENV) = dlsym_fun(libr_dll_handle, "PRENV");
+		*(void **)(&PRVALUE) = dlsym_fun(libr_dll_handle, "PRVALUE");
 		RK_DEBUG(RBACKEND, DL_DEBUG, "Falling back from R_GetBindingType to PRCODE (%p)", PRCODE);
 		RK_ASSERT(PRCODE);
 	}
+#	endif
+
+#else
+	RK_DEBUG(RBACKEND, DL_DEBUG, "R lib already linked");
 #endif
 }

From null at kde.org  Fri Apr 17 17:38:56 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 16:38:56 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Fix_compilation_with_R_=3C_4=2E5=2E0?=
Message-ID: <20260417163856.BBC9D1400E62@lerwini.kde.org>

Git commit fb32c2796831d56d5f4796e931714abfeb57f1a2 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 16:38.
Pushed by tfry into branch 'releases/0.8.3'.

Fix compilation with R < 4.5.0

M  +1    -1    rkward/rbackend/rkrapi.h
M  +1    -1    rkward/rbackend/rkstructuregetter.cpp

https://invent.kde.org/education/rkward/-/commit/fb32c2796831d56d5f4796e931714abfeb57f1a2

diff --git a/rkward/rbackend/rkrapi.h b/rkward/rbackend/rkrapi.h
index 06c9ca365..8e7ba33fe 100644
--- a/rkward/rbackend/rkrapi.h
+++ b/rkward/rbackend/rkrapi.h
@@ -186,6 +186,7 @@ class RFn : public QObject {
 	IMPORT_R_API(R_DelayedBindingExpression);
 	IMPORT_R_API(R_DelayedBindingEnvironment);
 	//IMPORT_R_API(R_ForcedBindingExpression);
+	IMPORT_R_API(R_getVar);
 	// these were removed in R 4.6, but we still need them
 	// if using a lower R at runtime
 	static inline SEXP (*PRCODE)(SEXP) = nullptr;
@@ -275,7 +276,6 @@ class RFn : public QObject {
 	IMPORT_R_API(Rf_findVarInFrame);
 	IMPORT_R_API(Rf_getAttrib);
 	IMPORT_R_API(Rf_getCharCE);
-	IMPORT_R_API(R_getVar);
 	IMPORT_R_API(Rf_initialize_R);
 	IMPORT_R_API(Rf_install);
 	IMPORT_R_API(Rf_installChar);
diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index a44161c62..cfb0e99b7 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -154,7 +154,7 @@ SEXP RKStructureGetter::peekFromEnv(SEXP sym, SEXP env) {
 	if (!RFn::PRCODE) {
 		// We have neither the new nor the old promise handling functions?
 		// fall back to forcing
-		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
+		return RFn::Rf_eval(sym, env); // R_getVar() may not be available, either
 	}
 	SEXP from = RFn::Rf_findVar(sym, env);
 	SEXP ret = from;

From null at kde.org  Fri Apr 17 17:47:56 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 17 Apr 2026 16:47:56 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_rkward/rbackend=3A_Yet_another_compilaiton_fix?=
Message-ID: <20260417164756.B4B701400E62@lerwini.kde.org>

Git commit 6cbb676972fcab70a8751e64a9c684e2fa70fb85 by Thomas Friedrichsmeier.
Committed on 17/04/2026 at 16:47.
Pushed by tfry into branch 'releases/0.8.3'.

Yet another compilaiton fix

M  +2    -2    rkward/rbackend/rkstructuregetter.cpp

https://invent.kde.org/education/rkward/-/commit/6cbb676972fcab70a8751e64a9c684e2fa70fb85

diff --git a/rkward/rbackend/rkstructuregetter.cpp b/rkward/rbackend/rkstructuregetter.cpp
index cfb0e99b7..8951a725d 100644
--- a/rkward/rbackend/rkstructuregetter.cpp
+++ b/rkward/rbackend/rkstructuregetter.cpp
@@ -134,10 +134,10 @@ void RKStructureGetter::getStructureSafe(SEXP value, const QString &name, int ad
 SEXP RKStructureGetter::peekFromEnv(SEXP sym, SEXP env) {
 	RK_TRACE(RBACKEND);
 
+#if R_VERSION >= R_Version(4, 6, 0)
 	if (keep_evalled_promises) {
 		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
 	}
-#if R_VERSION >= R_Version(4, 6, 0)
 	if (RFn::R_GetBindingType) { // implies R runtime >= 4.6
 		const auto type = RFn::R_GetBindingType(sym, env);
 		if (type == R_BindingTypeDelayed) {
@@ -151,7 +151,7 @@ SEXP RKStructureGetter::peekFromEnv(SEXP sym, SEXP env) {
 		return RFn::R_getVar(sym, env, /* inherits: */ FALSE);
 	}
 #endif
-	if (!RFn::PRCODE) {
+	if (keep_evalled_promises || !RFn::PRCODE) {
 		// We have neither the new nor the old promise handling functions?
 		// fall back to forcing
 		return RFn::Rf_eval(sym, env); // R_getVar() may not be available, either

From null at kde.org  Wed Apr 22 20:42:17 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Wed, 22 Apr 2026 19:42:17 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward/releases/0=2E8=2E3=5D_/=3A_Add_=28manual=29_CI_job_for_creating_and_test-compiling_source_release?=
Message-ID: <20260422194217.9981D1400263@lerwini.kde.org>

Git commit 290236626d38a2ae700378c93a5f874982c39f59 by Thomas Friedrichsmeier.
Committed on 22/04/2026 at 19:41.
Pushed by tfry into branch 'releases/0.8.3'.

Add (manual) CI job for creating and test-compiling source release

M  +35   -11   .gitlab-ci.yml

https://invent.kde.org/education/rkward/-/commit/290236626d38a2ae700378c93a5f874982c39f59

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c485d349a..84d2ddcf8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -40,27 +40,32 @@ clang_format:
 # setup mix-and-matched from okular, removing some extras
 # combined check for building on "old" distribution (old distro should ideally be the previous ubuntu LTS, but that does not have kf6, yet)
 # debian:stable has the advantage of being cached, and will likely remain stable for some time to come
-build_clazy_stable:
+.build_debian_stable:
   stage: build
   image: debian:stable
   tags:
     - Linux
-  only:
-    - merge_requests
   variables:
-    #CLAZY_CHECKS: level0,level1,level2,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch
-    #CXXFLAGS: -Werror -Wno-deprecated-declarations > rather using check_warnings, below
-    CC: clang
-    CXX: clazy
+    RKWARD_BUILDDEPS: cmake extra-cmake-modules qt6-webengine-dev libkf6notifications-dev gettext libkf6coreaddons-dev libkf6crash-dev libkf6guiaddons-dev libkf6i18n-dev libkf6iconthemes-dev libkf6breezeicons-dev libkf6texteditor-dev libkf6textwidgets-dev libkf6windowsystem-dev libkf6xmlgui-dev r-base-dev
   before_script:
     - apt-get update
     - apt-get install --yes eatmydata
-    - eatmydata apt-get install --yes --no-install-recommends clazy clang
-    # rkward build deps:
-    - eatmydata apt-get install --yes --no-install-recommends cmake extra-cmake-modules qt6-webengine-dev libkf6notifications-dev gettext libkf6coreaddons-dev libkf6crash-dev libkf6guiaddons-dev libkf6i18n-dev libkf6iconthemes-dev libkf6breezeicons-dev libkf6texteditor-dev libkf6textwidgets-dev libkf6windowsystem-dev libkf6xmlgui-dev r-base-dev
-  script:
     - export LC_ALL=C.UTF-8
+
+build_debian_stable_clazy:
+  extends: .build_debian_stable
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_DRAFT != "true"
+      when: always
+    - when: manual
+      allow_failure: true
+  script:
+    - eatmydata apt-get install --yes --no-install-recommends clazy clang $RKWARD_BUILDDEPS
     - mkdir -p build && cd build
+    #CLAZY_CHECKS: level0,level1,level2,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch
+    #CXXFLAGS: -Werror -Wno-deprecated-declarations > rather using check_warnings, below
+    - export CC=clang
+    - export CXX=clazy
     - cmake ..
     - make -j 9 kdsingleapplication # build first, to ignore all (not just clazy) warnings, in 3rdparty code
     - CLAZY_IGNORE_DIRS="3rdparty" make -j 9 > >(tee make.output) 2> >(tee make.error)
@@ -106,3 +111,22 @@ build_r_devel:
     - python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux/Qt6/Shared --extra-cmake-args=-DR_EXECUTABLE=$CI_PROJECT_DIR/rdir/install/bin/R --extra-cmake-args=-DDLOPEN_RLIB=0
  after_script:
     - ci-utilities/run-collect-crashes.py
+
+make_source_tar_gz:
+  extends: .build_debian_stable
+  rules:
+    - when: manual
+      allow_failure: true
+  script:
+    - eatmydata apt-get install --yes --no-install-recommends $RKWARD_BUILDDEPS r-cran-roxygen2 r-cran-devtools python3-polib rsync ninja-build
+    - mkdir dummy && cd dummy && cmake ..
+    - cd ..
+    - scripts/makedist.sh
+    - mkdir build && cd build
+    - tar -xvzf ../*.tar.gz
+    - cmake rkward-* -G Ninja
+    - ninja
+  artifacts:
+    paths:
+      - ./*.tar.gz
+    expire_in: 1 week

From null at kde.org  Wed Apr 22 20:43:42 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Wed, 22 Apr 2026 19:43:42 +0000 (UTC)
Subject: =?utf-8?q?=5Beducation/rkward=5D_/=3A_Add_=28manual=29_CI_job_for_creating_and_test-compiling_source_release?=
Message-ID: <20260422194342.07BA814008C6@lerwini.kde.org>

Git commit 6fc1513f806b0a0c29dfa0d69622036ec23aca89 by Thomas Friedrichsmeier.
Committed on 22/04/2026 at 19:43.
Pushed by tfry into branch 'master'.

Add (manual) CI job for creating and test-compiling source release

M  +35   -11   .gitlab-ci.yml

https://invent.kde.org/education/rkward/-/commit/6fc1513f806b0a0c29dfa0d69622036ec23aca89

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c485d349a..84d2ddcf8 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -40,27 +40,32 @@ clang_format:
 # setup mix-and-matched from okular, removing some extras
 # combined check for building on "old" distribution (old distro should ideally be the previous ubuntu LTS, but that does not have kf6, yet)
 # debian:stable has the advantage of being cached, and will likely remain stable for some time to come
-build_clazy_stable:
+.build_debian_stable:
   stage: build
   image: debian:stable
   tags:
     - Linux
-  only:
-    - merge_requests
   variables:
-    #CLAZY_CHECKS: level0,level1,level2,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch
-    #CXXFLAGS: -Werror -Wno-deprecated-declarations > rather using check_warnings, below
-    CC: clang
-    CXX: clazy
+    RKWARD_BUILDDEPS: cmake extra-cmake-modules qt6-webengine-dev libkf6notifications-dev gettext libkf6coreaddons-dev libkf6crash-dev libkf6guiaddons-dev libkf6i18n-dev libkf6iconthemes-dev libkf6breezeicons-dev libkf6texteditor-dev libkf6textwidgets-dev libkf6windowsystem-dev libkf6xmlgui-dev r-base-dev
   before_script:
     - apt-get update
     - apt-get install --yes eatmydata
-    - eatmydata apt-get install --yes --no-install-recommends clazy clang
-    # rkward build deps:
-    - eatmydata apt-get install --yes --no-install-recommends cmake extra-cmake-modules qt6-webengine-dev libkf6notifications-dev gettext libkf6coreaddons-dev libkf6crash-dev libkf6guiaddons-dev libkf6i18n-dev libkf6iconthemes-dev libkf6breezeicons-dev libkf6texteditor-dev libkf6textwidgets-dev libkf6windowsystem-dev libkf6xmlgui-dev r-base-dev
-  script:
     - export LC_ALL=C.UTF-8
+
+build_debian_stable_clazy:
+  extends: .build_debian_stable
+  rules:
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_DRAFT != "true"
+      when: always
+    - when: manual
+      allow_failure: true
+  script:
+    - eatmydata apt-get install --yes --no-install-recommends clazy clang $RKWARD_BUILDDEPS
     - mkdir -p build && cd build
+    #CLAZY_CHECKS: level0,level1,level2,no-ctor-missing-parent-argument,isempty-vs-count,qhash-with-char-pointer-key,raw-environment-function,qproperty-type-mismatch
+    #CXXFLAGS: -Werror -Wno-deprecated-declarations > rather using check_warnings, below
+    - export CC=clang
+    - export CXX=clazy
     - cmake ..
     - make -j 9 kdsingleapplication # build first, to ignore all (not just clazy) warnings, in 3rdparty code
     - CLAZY_IGNORE_DIRS="3rdparty" make -j 9 > >(tee make.output) 2> >(tee make.error)
@@ -106,3 +111,22 @@ build_r_devel:
     - python3 -u ci-utilities/run-ci-build.py --project $CI_PROJECT_NAME --branch $CI_COMMIT_REF_NAME --platform Linux/Qt6/Shared --extra-cmake-args=-DR_EXECUTABLE=$CI_PROJECT_DIR/rdir/install/bin/R --extra-cmake-args=-DDLOPEN_RLIB=0
  after_script:
     - ci-utilities/run-collect-crashes.py
+
+make_source_tar_gz:
+  extends: .build_debian_stable
+  rules:
+    - when: manual
+      allow_failure: true
+  script:
+    - eatmydata apt-get install --yes --no-install-recommends $RKWARD_BUILDDEPS r-cran-roxygen2 r-cran-devtools python3-polib rsync ninja-build
+    - mkdir dummy && cd dummy && cmake ..
+    - cd ..
+    - scripts/makedist.sh
+    - mkdir build && cd build
+    - tar -xvzf ../*.tar.gz
+    - cmake rkward-* -G Ninja
+    - ninja
+  artifacts:
+    paths:
+      - ./*.tar.gz
+    expire_in: 1 week

From null at kde.org  Fri Apr 24 14:05:29 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 24 Apr 2026 13:05:29 +0000 (UTC)
Subject: =?utf-8?q?=5Bwebsites/rkward-kde-org=5D_/=3A_Update_for_new_release?=
Message-ID: <20260424130529.7B94214000C8@lerwini.kde.org>

Git commit 277fbc62247e9ffae96afc3b3fb6af6158e147b9 by Thomas Friedrichsmeier.
Committed on 24/04/2026 at 06:54.
Pushed by tfry into branch 'master'.

Update for new release

M  +3    -3    config.yaml
A  +33   -0    content/news/rkward-0.8.3.md

https://invent.kde.org/websites/rkward-kde-org/-/commit/277fbc62247e9ffae96afc3b3fb6af6158e147b9

diff --git a/config.yaml b/config.yaml
index a5ac161..694a66c 100644
--- a/config.yaml
+++ b/config.yaml
@@ -45,8 +45,8 @@ params:
   rssSection: news
   showPatrons: false
   displayTitle: true
-  rkwardVersion: "0.8.2"
-  rkwardVersionDate: "Oct 01, 2025"
-  rkwardWindowsPatchLevel: "-1"
+  rkwardVersion: "0.8.3"
+  rkwardVersionDate: "Apr 24, 2026"
+  rkwardWindowsPatchLevel: ""
 rssLimit: 4
 summaryLength: 15
diff --git a/content/news/rkward-0.8.3.md b/content/news/rkward-0.8.3.md
new file mode 100644
index 0000000..d98ecc6
--- /dev/null
+++ b/content/news/rkward-0.8.3.md
@@ -0,0 +1,33 @@
+---
+title: RKWard 0.8.3
+date: 2026-04-24
+---
+
+RKWard 0.8.3, has been released, today. Official [downloads](/download) can be found in the usual place.
+Importantly this release addresses some incompatibility issues between RKWard 0.8.2 and the new R 4.6.0.
+RKWard 0.8.3 continues to work with older versions of R, but if you want to use RKWard R 4.6.0 or later,
+you will have to use RKWard version 0.8.3 or later.
+
+Beyond this, the new release does not bring many large user-visible changes. Under the hood, however, we
+now support a different HTML rendering engine for help and output files (QWebView in additino to QWebEngine).
+We hope that this will put an end to recent stability issues that have been reported for certain configurations.
+
+Advanced users may further appreciate the new option to define their own custom script preview modes.
+
+As usual, we're looking forward to your [feedback](/contact) suggestions, and contributions!
+
+#### The changes in detail:
+
+- New features and improvements
+   - Added: Allow to define custom script preview modes (rk.register.script.preview()))
+   - Added: Various small updates to the R highlighting in the R console
+   - Added: Allow translation of strings in the rkward R package
+   - Added: Support HTML rendering with QWebView, which allows to use native renderers on some platforms
+- Bug fixes
+   - Fixed: Remove obsolete dependency on dbus
+   - Fixed: Crash when splitting view on a data.frame editor window
+   - Fixed: Allow compilation with the upcoming R 4.6
+   - Fixed: Horizontal text alignment would be inaccurate for some symbols in RK() device
+   - Fixed: Parts of a command entered interactively could run twice under specific circumstances
+   - Fixed: Output window would fail to open, automatically, for certain plugins
+   - Fixed: Width of "progress" dialogs (showing R output) would exceed screen size depending on configuration


From null at kde.org  Fri Apr 24 14:05:29 2026
From: null at kde.org (Thomas Friedrichsmeier)
Date: Fri, 24 Apr 2026 13:05:29 +0000 (UTC)
Subject: =?utf-8?q?=5Bwebsites/rkward-kde-org=5D_/=3A_Assorted_fixes?=
Message-ID: <20260424130529.7C90D1400284@lerwini.kde.org>

Git commit 11d11829a964e175efcd5e56ee20a49228e5c7c0 by Thomas Friedrichsmeier.
Committed on 24/04/2026 at 07:20.
Pushed by tfry into branch 'master'.

Assorted fixes

M  +5    -1    config.yaml
M  +1    -1    content/download.md
M  +2    -2    content/rkward-on-mac.md
M  +4    -12   content/rkward-on-windows.md

https://invent.kde.org/websites/rkward-kde-org/-/commit/11d11829a964e175efcd5e56ee20a49228e5c7c0

diff --git a/config.yaml b/config.yaml
index 694a66c..5ea02a4 100644
--- a/config.yaml
+++ b/config.yaml
@@ -46,7 +46,11 @@ params:
   showPatrons: false
   displayTitle: true
   rkwardVersion: "0.8.3"
+  # adjust these for patch releases, if any
+  rkwardFullSourceVersion: "0.8.3"
+  rkwardFullWinVersion: "0.8.3"
+  rkwardFullMacVersion: "0.8.3"
+  rkwardFullAppimageVersion: "0.8.3"
   rkwardVersionDate: "Apr 24, 2026"
-  rkwardWindowsPatchLevel: ""
 rssLimit: 4
 summaryLength: 15
diff --git a/content/download.md b/content/download.md
index e2e2b4f..ce85ee5 100644
--- a/content/download.md
+++ b/content/download.md
@@ -12,7 +12,7 @@ menu:
 
 The latest official source packages. See [Building RKWard From Source](/building-rkward-from-source) for help on compiling.
 
-- **[rkward-{{% param rkwardVersion %}}](https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/rkward-{{% param rkwardVersion %}}.tar.gz)** (released {{% param rkwardVersionDate %}})
+- **[rkward-{{% param rkwardVersion %}}](https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/rkward-{{% param rkwardFullSourceVersion %}}.tar.gz)** (released {{% param rkwardVersionDate %}})
 - [ChangeLog](https://invent.kde.org/education/rkward/-/blob/releases/{{% param rkwardVersion %}}/ChangeLog)
 
 ### Compiled binary packages / build scripts
diff --git a/content/rkward-on-mac.md b/content/rkward-on-mac.md
index 0d75727..a8dff06 100644
--- a/content/rkward-on-mac.md
+++ b/content/rkward-on-mac.md
@@ -14,11 +14,11 @@ The 0.8.0 version of RKWard on Mac is known to crash during startup, occasionall
 ### Requirements
 
 * RKWard {{% param rkwardVersion %}} currently requires Mac OS X 10.15 or higher.
-* **You have to install R, separately**, version 4.2.0 or higher. Get it from <https://www.r-project.org/>. Make sure to 
+* **You have to install R, separately**, version 4.2.0 or higher. Get it from <https://www.r-project.org/>.
 
 ### Download latest official version
 
-* .dmg instalatation image of RKWard {{% param rkwardVersion %}} can be found at: <https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/>. Be sure to select the .dmg suitable for your architecture (x86 or arm). **This must match the architecture of your R installation**, even if your system supports both types of binary.
+* .dmg instalatation images of RKWard {{% param rkwardVersion %}} can be found at: <https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/>. Be sure to select the .dmg suitable for your architecture (x86 or arm). **This must match the architecture of your R installation**, even if your system supports both types of binary.
 
 ### Installation procedure
 
diff --git a/content/rkward-on-windows.md b/content/rkward-on-windows.md
index 90fa9ac..f8e7a11 100644
--- a/content/rkward-on-windows.md
+++ b/content/rkward-on-windows.md
@@ -4,7 +4,7 @@ title: Download and install RKWard on Windows
 
 ## Lastest official release
 
-* RKWard {{% param rkwardVersion %}} <https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/rkward-{{% param rkwardVersion %}}{{% param rkwardWindowsPatchLevel %}}-x86_64.exe> (includes R 4.3.3, but you can also use a separate installation of R)
+* RKWard {{% param rkwardVersion %}} <https://download.kde.org/stable/rkward/{{% param rkwardVersion %}}/rkward-{{% param rkwardFullWinVersion %}}-x86_64.exe> (includes R 4.5.3, but you can also use a separate installation of R)
 
 ## Installing the official release
 
@@ -17,18 +17,10 @@ Nightly development snapshots are available from
 these are built, automatically, and provided without any human testing.
 Back up your data, before using these.
 
-## Updating R inside the RKWard installation
+## Using a different version of R with your RKWard installation
 
-Since RKWard 0.7.1, updating R is often possible without any updates to RKWard, especially, for patch level releases of R (e.g. 4.1.**3** over 4.1.**2**).
-
-The recommended way to update R is to
-
-1. Install R as usual, in its default installation location.
-2. Open the file `[Your\RKWard\Installation\]\KDE\bin\rkward.ini`, and change
-   the path for "R_executable" to either "auto" or the full path to R.exe.
-3. In case of any problems revert the changes to rkward.ini.
-
-Alternatively, you can install your new version of R to `[Your\RKWard\Installation\]KDE\libs\R`. The potential advantage is that you will not have to reinstall your R packages after updating R (although it is still recommendable to do so).
+To use RKWard with a different version of R than contained in the package, just install that version of R, separately. Then, in RKWard select _Settings_->_Check installation_, and select that installation
+as the new backend.
 
 ## Compiling RKWard from source on Windows
 

From noreply at launchpad.net  Sat Apr 25 07:06:15 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 25 Apr 2026 06:06:15 -0000
Subject: [recipe build #4034112] of ~rkward-devel rkward-kf5-daily-r40 in
 jammy: Failed to build
Message-ID: <177709717507.3463350.16710780838413078003.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: jammy
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034112/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-016

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034112
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sat Apr 25 07:06:23 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 25 Apr 2026 06:06:23 -0000
Subject: [recipe build #4034118] of ~rkward-devel
 rkward-kf5-backports-daily-r35 in focal: Failed to build
Message-ID: <177709718388.3463350.12496006642274699055.launchpad@juju-98d295-prod-launchpad-51>

 * State: Failed to build
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r35
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r35
 * Distroseries: focal
 * Duration: 3 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r35/+recipebuild/4034118/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-055

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r35/+recipebuild/4034118
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sat Apr 25 07:06:45 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 25 Apr 2026 06:06:45 -0000
Subject: [recipe build #4034110] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177709720533.3463350.14582754050687445331.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034110/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-071

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034110
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sat Apr 25 07:07:42 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sat, 25 Apr 2026 06:07:42 -0000
Subject: [recipe build #4034121] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177709726267.3463350.9223218514595586114.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 4 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4034121/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-086

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4034121
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sun Apr 26 06:48:22 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sun, 26 Apr 2026 05:48:22 -0000
Subject: [recipe build #4034507] of ~rkward-devel
 rkward-kf5-backports-daily-r40 in bionic: Dependency wait
Message-ID: <177718250209.3463350.17017736185464885644.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-backports-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-backports-daily-r40
 * Distroseries: bionic
 * Duration: 3 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4034507/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-086

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-backports-daily-r40/+recipebuild/4034507
Your team RKWard Team is the requester of the build.


From noreply at launchpad.net  Sun Apr 26 06:48:21 2026
From: noreply at launchpad.net (noreply at launchpad.net)
Date: Sun, 26 Apr 2026 05:48:21 -0000
Subject: [recipe build #4034496] of ~rkward-devel rkward-kf5-daily-r40 in
 bionic: Dependency wait
Message-ID: <177718250169.3463350.1890236949468553197.launchpad@juju-98d295-prod-launchpad-51>

 * State: Dependency wait
 * Recipe: rkward-devel/rkward-kf5-daily-r40
 * Archive: ~rkward-devel/ubuntu/rkward-kf5-daily-r40
 * Distroseries: bionic
 * Duration: 3 minutes
 * Build Log: https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034496/+files/buildlog.txt.gz
 * Upload Log: 
 * Builder: https://launchpad.net/builders/lcy02-amd64-018

-- 
https://launchpad.net/~rkward-devel/+archive/ubuntu/rkward-kf5-daily-r40/+recipebuild/4034496
Your team RKWard Team is the requester of the build.


