[education/rkward] /: Implement callback from qwebview page to rkward, handle selection as proof of concept
Thomas Friedrichsmeier
null at kde.org
Thu Apr 9 20:44:24 BST 2026
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}));
+ }
+}
More information about the rkward-tracker
mailing list