Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a1a56cd08c | |||
| d9af82d230 | |||
| fb1a293e88 | |||
| 803d6edd41 | |||
| 35b7bfe2e9 | |||
| c6db000d26 | |||
| 85b5a29192 | |||
| b53597396c | |||
| be980dbffd | |||
| 8165ee20cc | |||
| cacffbcc2a | |||
| b4a4b2f554 | |||
| 2b107f8f73 | |||
| 490b89be38 | |||
| 2c57eb2ebf | |||
| 06d2cdb68f | |||
| 431ba3dffa | |||
| 0786a8eb0e | |||
| debda73068 | |||
| ce72177973 | |||
| 5214b9f925 | |||
| f37e89d956 | |||
| 89b084a6a7 | |||
| ee33c72915 | |||
| 59c316a1f0 | |||
| e9e295c3bc | |||
| 52aa0cac10 | |||
| 8e5381fda2 | |||
| 82f58bc24b | |||
| ef3883ed15 | |||
| 79d18bdd5d | |||
| 7468a16d63 | |||
| a5378e32b9 | |||
| 8ab8cb5335 |
@@ -73,3 +73,5 @@ CMakeLists.txt.user*
|
||||
*.exe
|
||||
|
||||
build
|
||||
lib/linux-x86_64
|
||||
lib/*.zip
|
||||
|
||||
+10
-20
@@ -12,19 +12,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets WebEngineWidgets)
|
||||
|
||||
add_library(rktwebview SHARED
|
||||
rktwebview_global.h
|
||||
rktwebview.h
|
||||
rktwebview.cpp
|
||||
shm.h shm.cpp
|
||||
shmqueue.h shmqueue.cpp
|
||||
rkt_protocol.h
|
||||
rktwebview_types.h
|
||||
json.cpp json.h
|
||||
utils.h
|
||||
utils.cpp
|
||||
)
|
||||
|
||||
add_executable(rktwebview_prg
|
||||
main.cpp
|
||||
|
||||
@@ -36,22 +23,25 @@ add_executable(rktwebview_prg
|
||||
|
||||
rktutils.h rktutils.cpp
|
||||
command.h command.cpp
|
||||
shm.h shm.cpp
|
||||
shmqueue.h shmqueue.cpp
|
||||
|
||||
rkt_protocol.h
|
||||
rktwebview_types.h
|
||||
utils.cpp utils.h
|
||||
)
|
||||
|
||||
add_executable(rktwebview_test
|
||||
rktwebview_test.cpp
|
||||
build_menu_from_json.cpp
|
||||
build_menu_from_json.h
|
||||
)
|
||||
|
||||
target_link_libraries(rktwebview_prg PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
|
||||
target_link_libraries(rktwebview_prg PRIVATE Qt${QT_VERSION_MAJOR}::WebEngineWidgets)
|
||||
|
||||
target_compile_definitions(rktwebview PRIVATE RKTWEBVIEW_LIBRARY)
|
||||
target_compile_definitions(rktwebview_prg PRIVATE RKTWEBVIEW_PRG_EXE)
|
||||
|
||||
target_link_Libraries(rktwebview_test PRIVATE rktwebview)
|
||||
add_executable(rktwebview_test
|
||||
rktwebview_test.cpp
|
||||
rkt_protocol.h
|
||||
)
|
||||
|
||||
target_link_libraries(rktwebview_test PRIVATE Qt${QT_VERSION_MAJOR}::Core)
|
||||
|
||||
add_dependencies(rktwebview_test rktwebview_prg)
|
||||
|
||||
@@ -1,3 +1,65 @@
|
||||
# racket-webview-qt
|
||||
|
||||
The Qt backend of racket-webview
|
||||
The Qt WebEngine backend of `racket-webview`.
|
||||
|
||||
## Version 0.2.3
|
||||
|
||||
This version replaces the shared-library/shared-memory transport with a
|
||||
standalone process interface.
|
||||
|
||||
## Transport
|
||||
|
||||
`rktwebview_prg` receives line-delimited JSON commands on stdin, writes
|
||||
line-delimited JSON results and events to stdout, and writes all diagnostics to
|
||||
stderr. The existing command numbers and command payloads in `rkt_protocol.h`
|
||||
are unchanged.
|
||||
|
||||
See `STDIO-PROTOCOL.md` for the transport envelope and shutdown rules.
|
||||
|
||||
The former shared library, shared-memory queue, and callback sources remain in
|
||||
the repository for comparison, but they are no longer part of the CMake target.
|
||||
|
||||
## Build
|
||||
|
||||
The CMake target requires Qt Widgets and Qt WebEngineWidgets and builds only:
|
||||
|
||||
```text
|
||||
rktwebview_prg
|
||||
```
|
||||
|
||||
## Previous Qt update
|
||||
|
||||
QtWebEngine select popup rendering issues were observed with older Qt 6.x. The
|
||||
native HTML `<select>` popup could keep growing or repainting while open while
|
||||
the QtWebEngine process remained busy. Upgrading to Qt 6.11.1 resolved this.
|
||||
|
||||
## Integration test
|
||||
|
||||
`rktwebview_test` starts the built `rktwebview_prg` with `QProcess` and tests
|
||||
the stdio handshake, request/result matching, basic webview commands, and clean
|
||||
shutdown. Run it from the build directory:
|
||||
|
||||
```sh
|
||||
./rktwebview_test
|
||||
```
|
||||
|
||||
For a protocol-only test that does not create a window:
|
||||
|
||||
```sh
|
||||
./rktwebview_test --protocol-only
|
||||
```
|
||||
|
||||
Set `RKT_WEBVIEW_PRG` to test a backend executable outside the test program's
|
||||
directory.
|
||||
|
||||
### Backend logging
|
||||
|
||||
Diagnostics written through `utils.h` use one-line structured stderr records:
|
||||
|
||||
```text
|
||||
json:{"topic":"webview-backend","level":"info","elapsed":0.125,"message":"..."}
|
||||
```
|
||||
|
||||
Stdout remains reserved for protocol messages. Unstructured stderr output from
|
||||
Qt or third-party libraries may still occur and should be treated as diagnostic
|
||||
text.
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
# racket-webview stdio protocol
|
||||
|
||||
The Qt backend is a child process of the Racket process. Communication uses
|
||||
UTF-8 JSON records, one compact JSON object per line.
|
||||
|
||||
- stdin of `rktwebview_prg`: commands from Racket
|
||||
- stdout of `rktwebview_prg`: handshake, results, and events
|
||||
- stderr of `rktwebview_prg`: diagnostics only
|
||||
|
||||
No diagnostic text may be written to stdout.
|
||||
|
||||
## Compatibility
|
||||
|
||||
The existing numeric command identifiers in `rkt_protocol.h` and their JSON
|
||||
payload objects are retained. The stdio transport adds only an envelope and a
|
||||
request identifier. The request identifier allows replies to be matched even
|
||||
when several Racket threads issue commands concurrently.
|
||||
|
||||
## Handshake
|
||||
|
||||
The backend writes this record after Qt has been initialized and the command
|
||||
reader has started:
|
||||
|
||||
```json
|
||||
{"type":"ready","protocol":1}
|
||||
```
|
||||
|
||||
Racket must not send application commands until this record has been received.
|
||||
|
||||
## Command
|
||||
|
||||
```json
|
||||
{"type":"command","id":17,"command":6,"data":{"wv":1,"url":"https://example.test"}}
|
||||
```
|
||||
|
||||
Fields:
|
||||
|
||||
- `id`: positive request identifier chosen by Racket
|
||||
- `command`: existing numeric command from `rkt_protocol.h`
|
||||
- `data`: existing command payload object
|
||||
|
||||
## Result
|
||||
|
||||
```json
|
||||
{"type":"result","id":17,"result":0,"data":null}
|
||||
```
|
||||
|
||||
`result` has the same meaning as before. Depending on the command it can be a
|
||||
`result_t`, a handle, a context identifier, a window-state value, or a metric.
|
||||
For `CMD_CALL_JS`, `data` contains the JavaScript result string.
|
||||
|
||||
Every command now receives a result record, including commands that previously
|
||||
returned `void`, such as close and setting the OU token. Their public Racket
|
||||
return values remain unchanged.
|
||||
|
||||
## Event
|
||||
|
||||
```json
|
||||
{"type":"event","wv":1,"data":"{\"event\":\"page-loaded\",...}"}
|
||||
```
|
||||
|
||||
The `data` field is the original event JSON string. It is deliberately not
|
||||
restructured by the transport, so existing event parsing remains unchanged.
|
||||
Events are independent of command results.
|
||||
|
||||
## Shutdown
|
||||
|
||||
A normal shutdown sends `CMD_QUIT`, waits for its result, closes the child's
|
||||
stdin, and waits for the child process. If the Racket process terminates
|
||||
unexpectedly, the operating system closes the pipe; EOF on stdin makes the Qt
|
||||
backend close all windows and quit.
|
||||
|
||||
## Structured stderr logging
|
||||
|
||||
The backend writes diagnostic logging to stderr, never to stdout. Log entries
|
||||
created through `utils.h` are emitted as one line beginning with `json:` followed
|
||||
by a compact JSON object:
|
||||
|
||||
```text
|
||||
json:{"topic":"webview-backend","level":"debug","elapsed":1.234,"message":"..."}
|
||||
```
|
||||
|
||||
Newlines and other control characters in the message are JSON escaped, so one
|
||||
C++ log call always produces one physical stderr line. Consumers should also
|
||||
accept unstructured stderr lines because Qt, Qt WebEngine, or another library
|
||||
may write diagnostics directly.
|
||||
@@ -0,0 +1,99 @@
|
||||
#include <QJsonArray>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonObject>
|
||||
#include <QMenu>
|
||||
#include "rktwebview_qt.h"
|
||||
#include "utils.h"
|
||||
|
||||
static QAction *addMenuItemFromJson(Rktwebview_qt *owner,
|
||||
QMenu *menu,
|
||||
const QJsonObject &item,
|
||||
int source_handle);
|
||||
|
||||
static QMenu *buildMenuObjectFromJson(Rktwebview_qt *owner,
|
||||
const QJsonArray &items,
|
||||
int source_handle,
|
||||
QWidget *parent = nullptr)
|
||||
{
|
||||
QMenu *menu = new QMenu(parent);
|
||||
|
||||
for (const QJsonValue &v : items) {
|
||||
if (!v.isObject()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QJsonObject item = v.toObject();
|
||||
|
||||
if (item.value("separator").toBool(false)) {
|
||||
menu->addSeparator();
|
||||
continue;
|
||||
}
|
||||
|
||||
addMenuItemFromJson(owner, menu, item, source_handle);
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
static QAction *addMenuItemFromJson(Rktwebview_qt *owner,
|
||||
QMenu *menu,
|
||||
const QJsonObject &item,
|
||||
int source_handle)
|
||||
{
|
||||
QString id = item.value("id").toString();
|
||||
QString name = item.value("name").toString();
|
||||
QString icon_file = item.value("icon").toString();
|
||||
|
||||
QAction *action = nullptr;
|
||||
|
||||
QJsonValue submenu_value = item.value("submenu");
|
||||
if (submenu_value.isObject()) {
|
||||
QJsonObject submenu_obj = submenu_value.toObject();
|
||||
QJsonArray submenu_items = submenu_obj.value("menu").toArray();
|
||||
|
||||
QMenu *submenu = buildMenuObjectFromJson(owner,
|
||||
submenu_items,
|
||||
source_handle,
|
||||
menu);
|
||||
|
||||
if (!icon_file.isEmpty()) {
|
||||
submenu->setIcon(QIcon(icon_file));
|
||||
}
|
||||
|
||||
submenu->setTitle(name);
|
||||
action = menu->addMenu(submenu);
|
||||
} else {
|
||||
if (icon_file.isEmpty()) {
|
||||
action = menu->addAction(name);
|
||||
} else {
|
||||
action = menu->addAction(QIcon(icon_file), name);
|
||||
}
|
||||
|
||||
QObject::connect(action, &QAction::triggered,
|
||||
owner, [owner, source_handle, id]() {
|
||||
EventContainer e("tray-menu-item-chosen");
|
||||
e["id"] = id;
|
||||
e["menu_item"] = id;
|
||||
owner->triggerEvent(source_handle, e);
|
||||
});
|
||||
}
|
||||
|
||||
action->setData(id);
|
||||
return action;
|
||||
}
|
||||
|
||||
QMenu *buildMenuFromJson(Rktwebview_qt *self, const QString &menu_json, int source_handle)
|
||||
{
|
||||
QJsonParseError err;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(menu_json.toUtf8(), &err);
|
||||
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
ERROR2("Json parse error for menu: %d, '%s'\n", err.error, err.errorString().toUtf8().constData());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QJsonObject root = doc.object();
|
||||
QJsonArray items = root.value("menu").toArray();
|
||||
|
||||
return buildMenuObjectFromJson(self, items, source_handle);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#ifndef BUILD_MENU_FROM_JSON_H
|
||||
#define BUILD_MENU_FROM_JSON_H
|
||||
|
||||
class QMenu;
|
||||
class Rktwebview_qt;
|
||||
class QString;
|
||||
|
||||
QMenu *buildMenuFromJson(Rktwebview_qt *self, const QString &menu_json, int source_handle);
|
||||
|
||||
#endif // BUILD_MENU_FROM_JSON_H
|
||||
@@ -28,6 +28,12 @@
|
||||
#define COMMAND_SET_OU_TOKEN 22
|
||||
#define COMMAND_NEW_CONTEXT 23
|
||||
#define COMMAND_MESSAGE 24
|
||||
#define COMMAND_SET_ICON 25
|
||||
#define COMMAND_CREATE_TRAY 26
|
||||
#define COMMAND_TRAY_SET_ICON 27
|
||||
#define COMMAND_TRAY_SET_TOOLTIP 28
|
||||
#define COMMAND_TRAY_SHOW_MESSAGE 29
|
||||
#define COMMAND_TRAY_SET_MENU 30
|
||||
|
||||
class Command
|
||||
{
|
||||
|
||||
Regular → Executable
+10
-5
@@ -2,20 +2,21 @@
|
||||
#
|
||||
ARCH=`uname -m`
|
||||
|
||||
LIB="lib/linux/$ARCH"
|
||||
LIB="../lib/linux-$ARCH"
|
||||
BUILD="../build/Release"
|
||||
|
||||
mkdir -p $LIB
|
||||
rm -f $LIB/*.so*
|
||||
cp build/Release/*.so $LIB
|
||||
cp build/Release/rktwebview_prg $LIB
|
||||
cp $BUILD/*.so $LIB
|
||||
cp $BUILD/rktwebview_prg $LIB
|
||||
|
||||
QT_PATH=`ldd build/Release/rktwebview_prg | grep Qt | awk '{print $3}' | head -1 | sed -e 's%[/]lib[/].*%%'`
|
||||
QT_PATH=`ldd $BUILD/rktwebview_prg | grep Qt | awk '{print $3}' | head -1 | sed -e 's%[/]lib[/].*%%'`
|
||||
echo "QT_PATH=$QT_PATH"
|
||||
|
||||
QT_PLUGINS="$QT_PATH/plugins"
|
||||
PLUGINS="platforms position generic iconengines imageformats qmltooling tls xcbglintegrations"
|
||||
|
||||
EXTRA_LIBS_SO=`ldd build/Release/rktwebview_prg | grep Qt | awk '{ print $3 }'`
|
||||
EXTRA_LIBS_SO=`ldd $BUILD/rktwebview_prg | grep Qt | awk '{ print $3 }'`
|
||||
EXTRA_LIBS_PLATFORM_PLUGIN_XCB=`ldd $QT_PATH/plugins/platforms/libqxcb.so | grep Qt | awk '{print $3}'`
|
||||
|
||||
for pl in $PLUGINS
|
||||
@@ -63,4 +64,8 @@ done
|
||||
|
||||
cp $QT_PATH/libexec/QtWebEngineProcess $LIB
|
||||
|
||||
(cd $LIB; cd ..; zip -9 -y -r linux-$ARCH.zip linux-$ARCH)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ const JSON &JSON::at(unsigned int index) const {
|
||||
|
||||
int JSON::length() const {
|
||||
if( Type == Class::Array )
|
||||
return Internal.List->size();
|
||||
return static_cast<int>(Internal.List->size());
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
@@ -105,9 +105,9 @@ bool JSON::hasKey(const std::string &key) const {
|
||||
|
||||
int JSON::size() const {
|
||||
if( Type == Class::Object )
|
||||
return Internal.Map->size();
|
||||
return static_cast<int>(Internal.Map->size());
|
||||
else if( Type == Class::Array )
|
||||
return Internal.List->size();
|
||||
return static_cast<int>(Internal.List->size());
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user