2 Commits

Author SHA1 Message Date
hans a1a56cd08c Ja. 2026-08-04 13:00:56 +02:00
hans d9af82d230 Omzetting naar stdio. 2026-08-03 08:22:42 +02:00
12 changed files with 927 additions and 589 deletions
+7 -22
View File
@@ -12,20 +12,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets) find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets WebEngineWidgets)
find_package(Qt${QT_VERSION_MAJOR} 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
memqueue.h memqueue.cpp
)
add_executable(rktwebview_prg add_executable(rktwebview_prg
main.cpp main.cpp
@@ -37,8 +23,6 @@ add_executable(rktwebview_prg
rktutils.h rktutils.cpp rktutils.h rktutils.cpp
command.h command.cpp command.h command.cpp
shm.h shm.cpp
shmqueue.h shmqueue.cpp
rkt_protocol.h rkt_protocol.h
rktwebview_types.h rktwebview_types.h
@@ -48,15 +32,16 @@ add_executable(rktwebview_prg
build_menu_from_json.h build_menu_from_json.h
) )
add_executable(rktwebview_test
rktwebview_test.cpp
)
target_link_libraries(rktwebview_prg PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) target_link_libraries(rktwebview_prg PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
target_link_libraries(rktwebview_prg PRIVATE Qt${QT_VERSION_MAJOR}::WebEngineWidgets) 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_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)
+60 -6
View File
@@ -1,11 +1,65 @@
# racket-webview-qt # racket-webview-qt
The Qt backend of racket-webview The Qt WebEngine backend of `racket-webview`.
# Version 0.2.2 ## Version 0.2.3
QtWebEngine select popup rendering issue observed with older Qt 6.x. This version replaces the shared-library/shared-memory transport with a
Symptom: native HTML <select> popup keeps growing/repainting while open; standalone process interface.
QtWebEngine process busy, Racket process idle.
Resolved by upgrading to Qt 6.11.1.
## 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.
+86
View File
@@ -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.
Binary file not shown.
Binary file not shown.
+337 -370
View File
@@ -1,494 +1,461 @@
#include <QThread>
#include <QFile>
#include <QApplication> #include <QApplication>
#include "rkt_protocol.h"
#include "shm.h"
#include "shmqueue.h"
#include "command.h"
#include "utils.h"
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonParseError>
#include <QThread>
#include <iostream>
#include <mutex>
#include <string>
#include "rkt_protocol.h"
#include "rktwebview_qt.h" #include "rktwebview_qt.h"
#include "utils.h"
namespace {
#ifdef DEBUG_RKT_WEBVIEW constexpr int STDIO_PROTOCOL_VERSION = 1;
#define WAIT_TIME (300 * 1000)
#else
#define WAIT_TIME (10 * 1000)
#endif
static void free_data(rkt_data_t *d) std::mutex output_mutex;
void writeMessage(const QJsonObject &message)
{ {
do_free_data(d); const QByteArray json = QJsonDocument(message).toJson(QJsonDocument::Compact);
std::lock_guard<std::mutex> lock(output_mutex);
std::cout.write(json.constData(), json.size());
std::cout.put('\n');
std::cout.flush();
}
void sendReady()
{
QJsonObject message;
message["type"] = "ready";
message["protocol"] = STDIO_PROTOCOL_VERSION;
writeMessage(message);
}
void sendResult(int id,
int result,
const QJsonValue &data = QJsonValue(QJsonValue::Null))
{
QJsonObject message;
message["type"] = "result";
message["id"] = id;
message["result"] = result;
message["data"] = data;
writeMessage(message);
}
void sendProtocolError(const QString &messageText, int id = -1)
{
QJsonObject message;
message["type"] = "protocol-error";
if (id >= 0) {
message["id"] = id;
}
message["message"] = messageText;
writeMessage(message);
}
void freeData(rkt_data_t *data)
{
do_free_data(data);
} }
class Handler : public QThread class Handler : public QThread
{ {
public: public:
Shm *shm; Rktwebview_qt *webviewHandler = nullptr;
ShmQueue *command_queue;
ShmQueue *result_queue;
ShmQueue *event_queue;
Rktwebview_qt *webview_handler;
bool quit;
// QThread interface
protected:
void run();
};
class Alive : public QThread
{
public:
Handler *handler;
ShmQueue *alive_queue;
ShmQueue *alive_ack_queue;
bool alive_timeout;
protected: protected:
void run(); void run() override;
}; };
static Handler *_handler;
static Alive *_alive;
static void event_cb(rkt_data_t *data) void eventCallback(rkt_data_t *data)
{ {
if (data->kind != rkt_data_kind_t::event) { if (data == nullptr || data->kind != rkt_data_kind_t::event) {
freeData(data);
return; return;
} }
int wv = data->data.event.wv; QJsonObject message;
char *evt = data->data.event.event; message["type"] = "event";
message["wv"] = data->data.event.wv;
message["data"] = QString::fromUtf8(data->data.event.event);
writeMessage(message);
std::string evt_d(evt); freeData(data);
_handler->event_queue->enqueue(wv, evt_d);
free_data(data);
} }
} // namespace
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
const char *me = argv[0]; INFO0("Starting stdio backend\n");
{ Handler commandHandler;
time_t my_time = time(NULL); commandHandler.webviewHandler = new Rktwebview_qt(argc, argv);
INFO1("Starting at %s", ctime(&my_time)); commandHandler.webviewHandler->initApp();
} commandHandler.start();
if (argc < 8) { sendReady();
ERROR1("%s: wrong number of arguments\n", me); commandHandler.webviewHandler->execApp();
exit(1);
}
const char *shm_name = argv[1]; INFO0("Qt event loop stopped; waiting for stdin handler\n");
const char *shm_size_str = argv[2]; commandHandler.wait();
const char *cmd_slot_str = argv[3];
const char *res_slot_str = argv[4];
const char *evt_slot_str = argv[5];
const char *alive_slot_str = argv[6];
const char *alive_ack_slot_str = argv[7];
size_t shm_size = atoi(shm_size_str); delete commandHandler.webviewHandler;
int cmd_slot = atoi(cmd_slot_str); commandHandler.webviewHandler = nullptr;
int res_slot = atoi(res_slot_str);
int evt_slot = atoi(evt_slot_str);
int alive_slot = atoi(alive_slot_str);
int alive_ack_slot = atoi(alive_ack_slot_str);
MKLOGSTMT(LOG_INFO, fprintf(stderr, "%s %s %s %s %s %s %s %s\n", me, shm_name, shm_size_str,
cmd_slot_str, res_slot_str, evt_slot_str, alive_slot_str, alive_ack_slot_str));
MKLOGSTMT(LOG_INFO, fprintf(stderr, "%s %s %d %d %d %d %d %d\n", me, shm_name, static_cast<int>(shm_size),
cmd_slot, res_slot, evt_slot, alive_slot, alive_ack_slot));
if (!(shm_size > 0 && cmd_slot > 0 && res_slot > 0 && evt_slot > 0 && alive_slot > 0 && alive_ack_slot > 0)) {
ERROR1("%s: Invalid shm size or slots\n", me);
exit(2);
}
Handler *handler = new Handler();
_handler = handler;
handler->shm = new Shm(shm_name, shm_size, false);
handler->command_queue = new ShmQueue(handler->shm, cmd_slot, false);
handler->result_queue = new ShmQueue(handler->shm, res_slot, false);
handler->event_queue = new ShmQueue(handler->shm, evt_slot, false);
handler->webview_handler = new Rktwebview_qt(argc, argv);
handler->start();
Alive *alive = new Alive();
_alive = alive;
alive->handler = handler;
alive->alive_timeout = false;
alive->alive_queue = new ShmQueue(handler->shm, alive_slot, false);
alive->alive_ack_queue = new ShmQueue(handler->shm, alive_ack_slot, false);
alive->start();
handler->webview_handler->initApp();
handler->webview_handler->execApp();
INFO0("waiting for thread to end\n");
handler->wait();
INFO0("Handler thread stopped\n");
alive->wait();
INFO0("Alive thread stopped\n");
if (alive->alive_timeout) {
// take ownership over all queues and SHM, because we didn't get an alive message
// and probably the racket side has crashed because of that.
ERROR0("Taking ownership of shared memory, shared queues and shared semaphores\n");
handler->command_queue->takeOwnership();
handler->result_queue->takeOwnership();
handler->event_queue->takeOwnership();
handler->shm->takeOwnership();
alive->alive_queue->takeOwnership();
alive->alive_ack_queue->takeOwnership();
}
INFO0("cleaning up shm\n");
delete alive->alive_queue;
delete alive;
delete handler->webview_handler;
delete handler->command_queue;
delete handler->result_queue;
delete handler->event_queue;
delete handler->shm;
delete handler;
{
time_t my_time = time(NULL);
INFO1("Exiting at %s", ctime(&my_time));
}
INFO0("Exiting stdio backend\n");
return 0; return 0;
} }
void Handler::run() void Handler::run()
{ {
int wait_ms = WAIT_TIME; // 10 seconds. std::string line;
while (!quit) { bool quit = false;
int cmd;
std::string data;
bool something_came = command_queue->dequeue(cmd, data, wait_ms);
if (!something_came) {
DEBUG1("No command received last %d seconds\n", wait_ms / 1000);
cmd = CMD_NOOP;
}
QJsonObject data_obj = QJsonDocument::fromJson(data.c_str()).object();
switch(cmd) { while (!quit && std::getline(std::cin, line)) {
case CMD_NOOP: { if (line.empty()) {
if (quit) { continue;
DEBUG1("Alive timeout, quit = %d\n", quit);
webview_handler->closeAllWindows();
DEBUG0("Closed all windows\n");
webview_handler->rktQuit();
DEBUG0("Quit application\n");
} }
QJsonParseError parseError;
const QJsonDocument document =
QJsonDocument::fromJson(QByteArray::fromStdString(line), &parseError);
if (parseError.error != QJsonParseError::NoError || !document.isObject()) {
const QString message = QString("Invalid protocol JSON: %1")
.arg(parseError.errorString());
ERROR1("%s\n", message.toUtf8().constData());
sendProtocolError(message);
continue;
} }
const QJsonObject request = document.object();
const int id = request["id"].toInt(-1);
if (request["type"].toString() != "command") {
sendProtocolError("Expected a command message", id);
continue;
}
const int command = request["command"].toInt(CMD_NOOP);
const QJsonObject data = request["data"].toObject();
if (id < 0) {
sendProtocolError("Command has no valid request id");
continue;
}
switch (command) {
case CMD_NOOP:
sendResult(id, result_t::oke);
break; break;
case CMD_QUIT: {
INFO0("Got quit message\n"); case CMD_QUIT:
webview_handler->rktQuit(); INFO0("Got quit command\n");
INFO0("Enqueing RESULT_QUIT to result queue\n"); webviewHandler->rktQuit();
result_queue->enqueue(RESULT_QUIT); sendResult(id, RESULT_QUIT);
quit = true; quit = true;
}
break; break;
case CMD_INFO: {
int open_windows = webview_handler->openWindows(); case CMD_INFO:
result_queue->enqueue(open_windows); sendResult(id, webviewHandler->openWindows());
}
break; break;
case CMD_HANDLE_IS_VALID: { case CMD_HANDLE_IS_VALID: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
bool oke = webview_handler->rktValid(wv); sendResult(id, webviewHandler->rktValid(wv) ? 1 : 0);
result_queue->enqueue(oke);
}
break; break;
}
case CMD_SET_LOGLEVEL: { case CMD_SET_LOGLEVEL: {
int ll = data_obj["wv"].toInt(); const int level = data["wv"].toInt();
setLogLevel(ll); setLogLevel(level);
bool oke = true; sendResult(id, result_t::oke);
result_queue->enqueue(oke);
}
break; break;
}
case CMD_CONTEXT_NEW: { case CMD_CONTEXT_NEW: {
QString boilerplate_js = data_obj["boilerplate_js"].toString(); const QString boilerplateJs = data["boilerplate_js"].toString();
bool has_pem_cert = data_obj["has_pem_cert"].toBool(); const bool hasPemCertificate = data["has_pem_cert"].toBool();
QString pem_cert = data_obj["pem_cert"].toString(); const QString pemCertificate = data["pem_cert"].toString();
int context = webview_handler->newContext(boilerplate_js, has_pem_cert, pem_cert); const int context = webviewHandler->newContext(boilerplateJs,
result_queue->enqueue(context); hasPemCertificate,
} pemCertificate);
sendResult(id, context);
break; break;
}
case CMD_CREATE_WV: { case CMD_CREATE_WV: {
int context = data_obj["context"].toInt(); const int context = data["context"].toInt();
int parent = data_obj["parent"].toInt(); const int parent = data["parent"].toInt();
const int wv = webviewHandler->rktWebViewCreate(context,
int wv = webview_handler->rktWebViewCreate(context, parent, event_cb); parent,
result_queue->enqueue(wv); eventCallback);
} sendResult(id, wv);
break; break;
}
case CMD_CLOSE_WV: { case CMD_CLOSE_WV: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
webview_handler->rktWebViewClose(wv); webviewHandler->rktWebViewClose(wv);
} sendResult(id, result_t::oke);
break; break;
}
case CMD_SET_URL: { case CMD_SET_URL: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString url = data_obj["url"].toString(); const QString url = data["url"].toString();
result_t r = webview_handler->rktSetUrl(wv, url.toUtf8().constData()); sendResult(id, webviewHandler->rktSetUrl(wv, url.toUtf8().constData()));
result_queue->enqueue(r);
}
break; break;
}
case CMD_SET_HTML: { case CMD_SET_HTML: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString html = data_obj["html"].toString(); const QString html = data["html"].toString();
result_t r = webview_handler->rktSetHtml(wv, html.toUtf8().constData()); sendResult(id, webviewHandler->rktSetHtml(wv, html.toUtf8().constData()));
result_queue->enqueue(r);
}
break; break;
}
case CMD_RUN_JS: { case CMD_RUN_JS: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString js = data_obj["js"].toString(); const QString js = data["js"].toString();
result_t r = webview_handler->rktRunJs(wv, js); sendResult(id, webviewHandler->rktRunJs(wv, js));
result_queue->enqueue(r);
}
break; break;
}
case CMD_CALL_JS: { case CMD_CALL_JS: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString js = data_obj["js"].toString(); const QString js = data["js"].toString();
rkt_data_t *res = webview_handler->rktCallJs(wv, js.toUtf8().constData()); rkt_data_t *response = webviewHandler->rktCallJs(wv, js.toUtf8().constData());
result_queue->enqueue(res->data.js_result.result, res->data.js_result.value); if (response == nullptr || response->kind != rkt_data_kind_t::js_result) {
free_data(res); sendResult(id, result_t::failed, "Missing JavaScript result");
} else {
sendResult(id,
response->data.js_result.result,
QString::fromUtf8(response->data.js_result.value));
} }
freeData(response);
break; break;
}
case CMD_OPEN_DEVTOOLS: { case CMD_OPEN_DEVTOOLS: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktOpenDevtools(wv); sendResult(id, webviewHandler->rktOpenDevtools(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_MOVE: { case CMD_MOVE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
int x = data_obj["x"].toInt(); sendResult(id,
int y = data_obj["y"].toInt(); webviewHandler->rktMove(wv,
result_t r = webview_handler->rktMove(wv, x, y); data["x"].toInt(),
result_queue->enqueue(r); data["y"].toInt()));
}
break; break;
}
case CMD_RESIZE: { case CMD_RESIZE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
int w = data_obj["w"].toInt(); sendResult(id,
int h = data_obj["h"].toInt(); webviewHandler->rktResize(wv,
result_t r = webview_handler->rktResize(wv, w, h); data["w"].toInt(),
result_queue->enqueue(r); data["h"].toInt()));
}
break; break;
}
case CMD_HIDE: { case CMD_HIDE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktHideWindow(wv); sendResult(id, webviewHandler->rktHideWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_SHOW: { case CMD_SHOW: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktShowWindow(wv); sendResult(id, webviewHandler->rktShowWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_PRESENT: { case CMD_PRESENT: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktPresentWindow(wv); sendResult(id, webviewHandler->rktPresentWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_MAXIMIZE: { case CMD_MAXIMIZE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktMaximizeWindow(wv); sendResult(id, webviewHandler->rktMaximizeWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_MINIMIZE: { case CMD_MINIMIZE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktMinimizeWindow(wv); sendResult(id, webviewHandler->rktMinimizeWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_SHOW_NORMAL: { case CMD_SHOW_NORMAL: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
result_t r = webview_handler->rktShowNormalWindow(wv); sendResult(id, webviewHandler->rktShowNormalWindow(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_WINDOW_STATE: { case CMD_WINDOW_STATE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
window_state_t r = webview_handler->rktWindowState(wv); sendResult(id, webviewHandler->rktWindowState(wv));
result_queue->enqueue(r);
}
break; break;
}
case CMD_SET_TITLE: { case CMD_SET_TITLE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
result_t r = webview_handler->rktWindowSetTitle(wv, title.toUtf8().constData()); sendResult(id,
result_queue->enqueue(r); webviewHandler->rktWindowSetTitle(wv,
} title.toUtf8().constData()));
break; break;
}
case CMD_SET_ICON: { case CMD_SET_ICON: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString icon_file = data_obj["icon"].toString(); const QString icon = data["icon"].toString();
result_t r = webview_handler->rktWindowSetIcon(wv, icon_file.toUtf8().constData()); sendResult(id,
result_queue->enqueue(r); webviewHandler->rktWindowSetIcon(wv,
} icon.toUtf8().constData()));
break; break;
}
case CMD_CREATE_TRAY: { case CMD_CREATE_TRAY: {
QString icon_file = data_obj["icon"].toString(); const QString icon = data["icon"].toString();
QString tooltip = data_obj["tooltip"].toString(); const QString tooltip = data["tooltip"].toString();
const int tray = webviewHandler->rktTrayCreate(icon.toUtf8().constData(),
int tray = webview_handler->rktTrayCreate(icon_file.toUtf8().constData(),
tooltip.toUtf8().constData(), tooltip.toUtf8().constData(),
event_cb); eventCallback);
result_queue->enqueue(tray); sendResult(id, tray);
}
break; break;
}
case CMD_TRAY_SET_ICON: { case CMD_TRAY_SET_ICON: {
int tray = data_obj["wv"].toInt(); const int tray = data["wv"].toInt();
QString icon_file = data_obj["icon"].toString(); const QString icon = data["icon"].toString();
sendResult(id,
result_t r = webview_handler->rktTraySetIcon(tray, webviewHandler->rktTraySetIcon(tray,
icon_file.toUtf8().constData()); icon.toUtf8().constData()));
result_queue->enqueue(r);
}
break; break;
}
case CMD_TRAY_SET_TOOLTIP: { case CMD_TRAY_SET_TOOLTIP: {
int tray = data_obj["wv"].toInt(); const int tray = data["wv"].toInt();
QString tooltip = data_obj["tooltip"].toString(); const QString tooltip = data["tooltip"].toString();
sendResult(id,
result_t r = webview_handler->rktTraySetTooltip(tray, webviewHandler->rktTraySetTooltip(tray,
tooltip.toUtf8().constData()); tooltip.toUtf8().constData()));
result_queue->enqueue(r);
}
break; break;
}
case CMD_TRAY_SHOW_MESSAGE: { case CMD_TRAY_SHOW_MESSAGE: {
int tray = data_obj["wv"].toInt(); const int tray = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
QString message = data_obj["message"].toString(); const QString message = data["message"].toString();
sendResult(id,
result_t r = webview_handler->rktTrayShowMessage(tray, webviewHandler->rktTrayShowMessage(tray,
title.toUtf8().constData(), title.toUtf8().constData(),
message.toUtf8().constData()); message.toUtf8().constData()));
result_queue->enqueue(r);
}
break; break;
}
case CMD_TRAY_CLEAR_MENU: { case CMD_TRAY_CLEAR_MENU: {
int tray = data_obj["wv"].toInt(); const int tray = data["wv"].toInt();
result_t r = webview_handler->rktTraySetMenu(tray, nullptr); sendResult(id, webviewHandler->rktTraySetMenu(tray, nullptr));
result_queue->enqueue(r);
}
break; break;
case CMD_TRAY_SET_MENU: { }
int tray = data_obj["wv"].toInt();
QString menu_json = data_obj["menu_json"].toString();
result_t r = webview_handler->rktTraySetMenu(tray, case CMD_TRAY_SET_MENU: {
menu_json.toUtf8().constData()); const int tray = data["wv"].toInt();
result_queue->enqueue(r); const QString menuJson = data["menu_json"].toString();
} sendResult(id,
webviewHandler->rktTraySetMenu(tray,
menuJson.toUtf8().constData()));
break; break;
}
case CMD_CHOOSE_DIR: { case CMD_CHOOSE_DIR: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
QString base_dir = data_obj["base_dir"].toString(); const QString baseDir = data["base_dir"].toString();
result_t r = webview_handler->rktChooseDir(wv, sendResult(id,
webviewHandler->rktChooseDir(wv,
title.toUtf8().constData(), title.toUtf8().constData(),
base_dir.toUtf8().constData() baseDir.toUtf8().constData()));
);
result_queue->enqueue(r);
}
break; break;
}
case CMD_FILE_OPEN: { case CMD_FILE_OPEN: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
QString base_dir = data_obj["base_dir"].toString(); const QString baseDir = data["base_dir"].toString();
QString permitted_exts = data_obj["permitted_exts"].toString(); const QString permittedExtensions = data["permitted_exts"].toString();
result_t r = webview_handler->rktFileOpen(wv, sendResult(id,
webviewHandler->rktFileOpen(wv,
title.toUtf8().constData(), title.toUtf8().constData(),
base_dir.toUtf8().constData(), baseDir.toUtf8().constData(),
permitted_exts.toUtf8().constData() permittedExtensions.toUtf8().constData()));
);
result_queue->enqueue(r);
}
break; break;
}
case CMD_FILE_SAVE: { case CMD_FILE_SAVE: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
QString base_dir = data_obj["base_dir"].toString(); const QString baseDir = data["base_dir"].toString();
QString permitted_exts = data_obj["permitted_exts"].toString(); const QString permittedExtensions = data["permitted_exts"].toString();
result_t r = webview_handler->rktFileSave(wv, sendResult(id,
webviewHandler->rktFileSave(wv,
title.toUtf8().constData(), title.toUtf8().constData(),
base_dir.toUtf8().constData(), baseDir.toUtf8().constData(),
permitted_exts.toUtf8().constData() permittedExtensions.toUtf8().constData()));
);
result_queue->enqueue(r);
}
break; break;
}
case CMD_SET_OU_TOKEN: { case CMD_SET_OU_TOKEN: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString token = data_obj["token"].toString(); const QString token = data["token"].toString();
webview_handler->rktSetOUToken(wv, token.toUtf8().constData()); webviewHandler->rktSetOUToken(wv, token.toUtf8().constData());
} sendResult(id, result_t::oke);
break; break;
}
case CMD_MSG_BOX: { case CMD_MSG_BOX: {
int wv = data_obj["wv"].toInt(); const int wv = data["wv"].toInt();
QString title = data_obj["title"].toString(); const QString title = data["title"].toString();
QString message = data_obj["message"].toString(); const QString message = data["message"].toString();
QString submsg = data_obj["submessage"].toString(); const QString submessage = data["submessage"].toString();
int type = data_obj["type"].toInt(); const auto type = static_cast<rkt_messagetype_t>(data["type"].toInt());
result_t r = webview_handler->rktMessageBox(wv, sendResult(id,
webviewHandler->rktMessageBox(wv,
title.toUtf8().constData(), title.toUtf8().constData(),
message.toUtf8().constData(), message.toUtf8().constData(),
submsg.toUtf8().constData(), submessage.toUtf8().constData(),
static_cast<rkt_messagetype_t>(type) type));
);
result_queue->enqueue(r);
}
break; break;
default: {
ERROR1("Unknown command: %d\n", cmd);
}
}
}
DEBUG0("Exiting handler thread\n");
} }
void Alive::run() default: {
{ const QString message = QString("Unknown command: %1").arg(command);
int wait_ms = WAIT_TIME; ERROR1("%s\n", message.toUtf8().constData());
int ping_no; sendResult(id, result_t::failed, message);
std::string data; break;
bool go_on = true;
while (go_on) {
bool something_came = alive_queue->dequeue(ping_no, data, wait_ms);
if (!something_came) {
ERROR0("No alive message received, stopping alive loop and quitting rktwebview_prg\n");
handler->quit = true;
handler->command_queue->enqueue(CMD_NOOP);
alive_timeout = true;
go_on = false;
} else {
if (ping_no == CMD_ALIVE_QUIT) {
DEBUG0("Got Quit ping for alive thread\n");
go_on = false;
} else {
DEBUG1("Got alive ping: %d\n", ping_no);
alive_ack_queue->enqueue(ping_no);
} }
} }
} }
DEBUG0("Exiting alive thread\n");
if (!quit) {
INFO0("stdin closed; stopping Qt backend\n");
webviewHandler->closeAllWindows();
webviewHandler->rktQuit();
}
DEBUG0("Exiting stdin handler\n");
} }
+4
View File
@@ -1,6 +1,10 @@
#ifndef RKT_PROTOCOL_H #ifndef RKT_PROTOCOL_H
#define RKT_PROTOCOL_H #define RKT_PROTOCOL_H
// The numeric command identifiers and payload objects are shared by both
// transports. The active transport wraps each command in one JSON line; see
// STDIO-PROTOCOL.md.
#define CMD_HANDLE_IS_VALID 1 #define CMD_HANDLE_IS_VALID 1
#define CMD_QUIT 2 #define CMD_QUIT 2
#define CMD_CONTEXT_NEW 3 // arguments: boilerplate_js: string, has_cert: bool, cert_pem: string -> context: int #define CMD_CONTEXT_NEW 3 // arguments: boilerplate_js: string, has_cert: bool, cert_pem: string -> context: int
+1 -1
View File
@@ -5,7 +5,7 @@
#include "rktwebview_types.h" #include "rktwebview_types.h"
#define RKT_WEBVIEW_API_MAJOR 0 #define RKT_WEBVIEW_API_MAJOR 0
#define RKT_WEBVIEW_API_MINOR 3 #define RKT_WEBVIEW_API_MINOR 1
#define RKT_WEBVIEW_API_PATCH 1 #define RKT_WEBVIEW_API_PATCH 1
extern "C" { extern "C" {
+5 -6
View File
@@ -181,14 +181,13 @@ void Rktwebview_qt::processCommand(Command *cmd)
int wv = cmd->args[0].toInt(); int wv = cmd->args[0].toInt();
if (_views.contains(wv)) { if (_views.contains(wv)) {
WebviewWindow *w = _views[wv]; WebviewWindow *w = _views[wv];
_views.remove(wv);
// WebviewWindow::closeEvent removes the view administration and
// schedules the window and web view with deleteLater(). Do not
// inspect or delete w after closeView(): processing the close
// event can already have scheduled or performed its destruction.
w->closeView(); w->closeView();
cmd->result = true; cmd->result = true;
while(w->isVisible()) {
_app->processEvents();
}
_view_js_callbacks.remove(wv);
delete w;
} else if (_trays.contains(wv)) { } else if (_trays.contains(wv)) {
QSystemTrayIcon *tray = _trays[wv]; QSystemTrayIcon *tray = _trays[wv];
_trays.remove(wv); _trays.remove(wv);
+280 -73
View File
@@ -1,94 +1,301 @@
#include "rktwebview.h" #include <QCoreApplication>
#include <string> #include <QElapsedTimer>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QStringList>
#ifdef _WIN32 #include <cstdio>
#include <windows.h> #include <utility>
#else
#include <unistd.h>
#endif
#include "utils.h"
void evt_cb(int n) #include "rkt_protocol.h"
namespace {
constexpr int protocolVersion = 1;
constexpr int timeoutMs = 10000;
class ProtocolClient
{ {
fprintf(stderr, "events waiting: %d - %d\n", n, rkt_webview_events_waiting()); public:
explicit ProtocolClient(QString program)
: program_(std::move(program))
{
process_.setProcessChannelMode(QProcess::SeparateChannels);
} }
bool start()
{
process_.start(program_, {});
if (!process_.waitForStarted(timeoutMs)) {
fail(QString("Could not start %1: %2")
.arg(program_, process_.errorString()));
return false;
}
const QJsonObject ready = readMessage("ready", -1);
if (ready.isEmpty()) {
return false;
}
if (ready["protocol"].toInt(-1) != protocolVersion) {
fail(QString("Unexpected protocol version: %1")
.arg(ready["protocol"].toInt(-1)));
return false;
}
std::fprintf(stderr, "PASS: backend ready (protocol %d)\n", protocolVersion);
return true;
}
QJsonObject command(int command, const QJsonObject &data = {})
{
const int id = nextId_++;
QJsonObject request;
request["type"] = "command";
request["id"] = id;
request["command"] = command;
request["data"] = data;
const QByteArray json = QJsonDocument(request).toJson(QJsonDocument::Compact) + '\n';
if (process_.write(json) != json.size() || !process_.waitForBytesWritten(timeoutMs)) {
fail(QString("Could not write command %1: %2")
.arg(command)
.arg(process_.errorString()));
return {};
}
return readMessage("result", id);
}
bool stop()
{
const QJsonObject result = command(CMD_QUIT);
if (result.isEmpty() || result["result"].toInt() != RESULT_QUIT) {
fail("QUIT did not return RESULT_QUIT");
process_.kill();
process_.waitForFinished(timeoutMs);
return false;
}
process_.closeWriteChannel();
if (!process_.waitForFinished(timeoutMs)) {
fail("Backend did not stop after QUIT");
process_.kill();
process_.waitForFinished(timeoutMs);
return false;
}
drainStderr();
std::fprintf(stderr, "PASS: backend stopped cleanly\n");
return process_.exitStatus() == QProcess::NormalExit && process_.exitCode() == 0;
}
void abort()
{
if (process_.state() != QProcess::NotRunning) {
process_.kill();
process_.waitForFinished(timeoutMs);
}
drainStderr();
}
private:
QJsonObject readMessage(const QString &expectedType, int expectedId)
{
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < timeoutMs) {
while (process_.canReadLine()) {
const QByteArray line = process_.readLine().trimmed();
if (line.isEmpty()) {
continue;
}
QJsonParseError error;
const QJsonDocument document = QJsonDocument::fromJson(line, &error);
if (error.error != QJsonParseError::NoError || !document.isObject()) {
fail(QString("Invalid JSON from backend: %1").arg(QString::fromUtf8(line)));
return {};
}
const QJsonObject message = document.object();
const QString type = message["type"].toString();
if (type == "event") {
std::fprintf(stderr,
"EVENT: webview=%d %s\n",
message["wv"].toInt(),
message["data"].toString().toUtf8().constData());
continue;
}
if (type == "protocol-error") {
fail(QString("Protocol error: %1").arg(message["message"].toString()));
return {};
}
if (type != expectedType) {
fail(QString("Expected message type %1, got %2")
.arg(expectedType, type));
return {};
}
if (expectedId >= 0 && message["id"].toInt(-1) != expectedId) {
fail(QString("Expected result id %1, got %2")
.arg(expectedId)
.arg(message["id"].toInt(-1)));
return {};
}
return message;
}
drainStderr();
if (process_.state() == QProcess::NotRunning) {
fail(QString("Backend stopped unexpectedly with exit code %1")
.arg(process_.exitCode()));
return {};
}
process_.waitForReadyRead(100);
}
fail(QString("Timed out waiting for %1").arg(expectedType));
return {};
}
void drainStderr()
{
const QByteArray diagnostics = process_.readAllStandardError();
if (!diagnostics.isEmpty()) {
std::fwrite(diagnostics.constData(), 1, diagnostics.size(), stderr);
}
}
static void fail(const QString &message)
{
std::fprintf(stderr, "FAIL: %s\n", message.toUtf8().constData());
}
QString program_;
QProcess process_;
int nextId_ = 1;
};
bool expectResult(const QJsonObject &message, int expected, const char *description)
{
if (message.isEmpty()) {
return false;
}
const int actual = message["result"].toInt();
if (actual != expected) {
std::fprintf(stderr,
"FAIL: %s returned %d, expected %d\n",
description,
actual,
expected);
return false;
}
std::fprintf(stderr, "PASS: %s\n", description);
return true;
}
QString backendProgram(const QCoreApplication &app)
{
const QString environmentProgram = qEnvironmentVariable("RKT_WEBVIEW_PRG");
if (!environmentProgram.isEmpty()) {
return environmentProgram;
}
#ifdef Q_OS_WIN
return app.applicationDirPath() + "/rktwebview_prg.exe";
#else
return app.applicationDirPath() + "/rktwebview_prg";
#endif
}
} // namespace
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
std::string me = argv[0]; QCoreApplication app(argc, argv);
const bool protocolOnly = app.arguments().contains("--protocol-only");
const QString program = backendProgram(app);
std::string loc = basedir(me); if (!QFileInfo::exists(program)) {
#ifdef _WIN32 std::fprintf(stderr,
std::string prg = loc + "\\rktwebview_prg.exe"; "FAIL: backend executable not found: %s\n",
SetDllDirectoryA("C:\\Qt\\6.11.1\\msvc2022_64\\bin"); program.toUtf8().constData());
#else return 1;
std::string prg = loc + "/rktwebview_prg";
#endif
#ifdef _WIN32
{
std::string e = std::string("RKT_WEBVIEW_PRG=") + prg;
_putenv(e.c_str());
} }
#else
setenv("RKT_WEBVIEW_PRG", prg.c_str(), true);
setenv("LD_LIBRARY_PATH", loc.c_str(), true);
#endif
rkt_webview_init(__FUNCTION__); ProtocolClient client(program);
if (!client.start()) {
client.abort();
return 1;
}
int context = rkt_webview_new_context("", nullptr); bool ok = true;
rkt_webview_set_loglevel(rkt_webview_loglevel_t::log_debug); ok = expectResult(client.command(CMD_NOOP), 0, "NOOP") && ok;
const char *icon_file = "../../resource/rktplayer.png"; const QJsonObject initialInfo = client.command(CMD_INFO);
FILE *f = fopen(icon_file, "rb"); ok = expectResult(initialInfo, 0, "INFO reports no open windows") && ok;
if (f == nullptr) {
WARN1("Cannot find icon file %s\n", icon_file); if (!protocolOnly && ok) {
QJsonObject contextData;
contextData["boilerplate_js"] = "";
contextData["has_pem_cert"] = false;
contextData["pem_cert"] = "";
const QJsonObject contextResult = client.command(CMD_CONTEXT_NEW, contextData);
const int context = contextResult["result"].toInt(-1);
if (context < 0) {
std::fprintf(stderr, "FAIL: CONTEXT_NEW returned %d\n", context);
ok = false;
} else { } else {
fclose(f); std::fprintf(stderr, "PASS: context created (%d)\n", context);
} }
int tray_wv = rkt_webview_tray_create(icon_file, "This is a test tray icon");
INFO1("tray_wv = %d\n", tray_wv);
rkt_webview_register_evt_callback(evt_cb); QJsonObject createData;
createData["context"] = context;
int wv = rkt_webview_create(context, 0); createData["parent"] = 0;
rkt_webview_set_title(wv, "Hi there, this is a title!"); const QJsonObject createResult = client.command(CMD_CREATE_WV, createData);
rkt_webview_set_icon(wv, "../../rktplayer.png"); const int webview = createResult["result"].toInt(-1);
if (webview <= 0) {
rkt_data_t *d = rkt_webview_info(); std::fprintf(stderr, "FAIL: CREATE_WV returned %d\n", webview);
rkt_webview_free_data(d); ok = false;
} else {
rkt_webview_move(wv, 100, 200); std::fprintf(stderr, "PASS: webview created (%d)\n", webview);
rkt_webview_resize(wv, 800, 600);
//rkt_webview_set_url(wv, "https://wikipedia.org");
rkt_webview_set_html(wv, "<html><head><title>Hi!</title></head><body><h1>Oke test</h1><select id=\"sel-lang\"><option value=\"nl\" selected>Nederlands</option><option value=\"en\">English</option></select><p>Ja</p></body></html>");
d = rkt_webview_info();
fprintf(stderr, "%s\n", d->data.metrics.log_file);
rkt_webview_free_data(d);
while(rkt_webview_events_waiting() > 0) {
rkt_data_t *d = rkt_webview_get_event();
rkt_webview_free_data(d);
} }
#ifdef _WIN32
Sleep(15000);
rkt_webview_tray_show_message(tray_wv, "This is a title", "This is a message to display<br><b>Hopefully</b> it does display.");
Sleep(15000);
#else
sleep(30
);
#endif
d = rkt_webview_info();
rkt_webview_free_data(d);
rkt_webview_close(wv); if (ok) {
QJsonObject titleData;
titleData["wv"] = webview;
titleData["title"] = "rktwebview stdio integration test";
ok = expectResult(client.command(CMD_SET_TITLE, titleData), 0, "SET_TITLE") && ok;
d = rkt_webview_info(); QJsonObject htmlData;
rkt_webview_free_data(d); htmlData["wv"] = webview;
htmlData["html"] = "<html><head><title>stdio test</title></head>"
"<body><h1>rktwebview stdio test</h1></body></html>";
ok = expectResult(client.command(CMD_SET_HTML, htmlData), 0, "SET_HTML") && ok;
rkt_webview_close(tray_wv); QJsonObject validData;
validData["wv"] = webview;
ok = expectResult(client.command(CMD_HANDLE_IS_VALID, validData), 1,
"HANDLE_IS_VALID before close") && ok;
rkt_webview_cleanup(); QJsonObject closeData;
closeData["wv"] = webview;
ok = expectResult(client.command(CMD_CLOSE_WV, closeData), 0, "CLOSE_WV") && ok;
}
}
const bool stopped = client.stop();
return ok && stopped ? 0 : 1;
} }
+54 -14
View File
@@ -1,17 +1,36 @@
#include "utils.h" #include "utils.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QString>
#include <chrono> #include <chrono>
#include <cstdarg>
#include <cstdio>
#include <vector>
typedef struct { typedef struct {
std::chrono::time_point<std::chrono::system_clock> start; std::chrono::time_point<std::chrono::system_clock> start;
} timer; } timer;
static int _log_level = LOG_INFO; static int _log_level = LOG_INFO;
static timer *_timer = nullptr; static timer *_timer = nullptr;
static QString formattedMessage(const char *format, va_list args)
{
va_list countArgs;
va_copy(countArgs, args);
const int length = std::vsnprintf(nullptr, 0, format, countArgs);
va_end(countArgs);
if (length < 0) {
return QString::fromUtf8(format);
}
std::vector<char> buffer(static_cast<size_t>(length) + 1);
std::vsnprintf(buffer.data(), buffer.size(), format, args);
return QString::fromUtf8(buffer.data(), length);
}
extern "C" { extern "C" {
int logLevel() int logLevel()
@@ -27,27 +46,48 @@ void setLogLevel(int l)
const char *logIndicator(int l) const char *logIndicator(int l)
{ {
switch(l) { switch(l) {
case LOG_ERROR: return "ERROR "; case LOG_ERROR: return "error";
case LOG_INFO: return "INFO "; case LOG_INFO: return "info";
case LOG_DEBUG: return "DEBUG "; case LOG_DEBUG: return "debug";
case LOG_WARNING: return "WARNING"; case LOG_WARNING: return "warning";
} }
return "UNKNOWN"; return "unknown";
} }
void logElapsed() double logElapsedSeconds()
{ {
if (_timer == nullptr) { if (_timer == nullptr) {
_timer = new timer; _timer = new timer;
_timer->start = std::chrono::system_clock::now(); _timer->start = std::chrono::system_clock::now();
} }
auto c = std::chrono::system_clock::now(); const auto current = std::chrono::system_clock::now();
auto duration = c - _timer->start; const auto milliseconds
auto milliseconds
= std::chrono::duration_cast<std::chrono::milliseconds>( = std::chrono::duration_cast<std::chrono::milliseconds>(
duration) current - _timer->start)
.count(); .count();
fprintf(stderr, "%8.3lf: ", milliseconds/ 1000.0); return milliseconds / 1000.0;
}
void logMessage(int level, const char *format, ...)
{
if (logLevel() < level) {
return;
}
va_list args;
va_start(args, format);
const QString message = formattedMessage(format, args);
va_end(args);
QJsonObject object;
object.insert(QStringLiteral("topic"), QStringLiteral("webview-backend"));
object.insert(QStringLiteral("level"), QString::fromLatin1(logIndicator(level)));
object.insert(QStringLiteral("elapsed"), logElapsedSeconds());
object.insert(QStringLiteral("message"), message);
const QByteArray json = QJsonDocument(object).toJson(QJsonDocument::Compact);
std::fprintf(stderr, "json:%s\n", json.constData());
std::fflush(stderr);
} }
} }
+16 -20
View File
@@ -29,6 +29,7 @@ inline std::string basedir(const std::string &path)
} }
return r; return r;
} }
extern "C" { extern "C" {
#ifdef RKTWEBVIEW_PRG_EXE #ifdef RKTWEBVIEW_PRG_EXE
#undef RKTWEBVIEW_EXPORT #undef RKTWEBVIEW_EXPORT
@@ -37,8 +38,9 @@ extern "C" {
RKTWEBVIEW_EXPORT int logLevel(); RKTWEBVIEW_EXPORT int logLevel();
RKTWEBVIEW_EXPORT void setLogLevel(int l); RKTWEBVIEW_EXPORT void setLogLevel(int l);
RKTWEBVIEW_EXPORT void logElapsed(); RKTWEBVIEW_EXPORT double logElapsedSeconds();
RKTWEBVIEW_EXPORT const char *logIndicator(int l); RKTWEBVIEW_EXPORT const char *logIndicator(int l);
RKTWEBVIEW_EXPORT void logMessage(int level, const char *format, ...);
} }
#define LOG_ERROR 1 #define LOG_ERROR 1
@@ -46,28 +48,22 @@ RKTWEBVIEW_EXPORT const char *logIndicator(int l);
#define LOG_INFO 3 #define LOG_INFO 3
#define LOG_DEBUG 4 #define LOG_DEBUG 4
#define MKLOGSTMT(level, code) if (logLevel() >= level) { fprintf(stderr, "%s: ", logIndicator(level));logElapsed();code;fflush(stderr); } #define ERROR0(msg) logMessage(LOG_ERROR, msg)
#define MKL0(level, msg) MKLOGSTMT(level, fprintf(stderr, msg)) #define WARN0(msg) logMessage(LOG_WARNING, msg)
#define MKL1(level, msg, a) MKLOGSTMT(level, fprintf(stderr, msg, a)) #define INFO0(msg) logMessage(LOG_INFO, msg)
#define MKL2(level, msg, a, b) MKLOGSTMT(level, fprintf(stderr, msg, a, b)) #define DEBUG0(msg) logMessage(LOG_DEBUG, msg)
#define MKL3(level, msg, a, b, c) MKLOGSTMT(level, fprintf(stderr, msg, a, b, c))
#define ERROR0(msg) MKL0(LOG_ERROR, msg) #define ERROR1(msg, a) logMessage(LOG_ERROR, msg, a)
#define WARN0(msg) MKL0(LOG_WARNING, msg) #define WARN1(msg, a) logMessage(LOG_WARNING, msg, a)
#define INFO0(msg) MKL0(LOG_INFO, msg) #define INFO1(msg, a) logMessage(LOG_INFO, msg, a)
#define DEBUG0(msg) MKL0(LOG_DEBUG, msg) #define DEBUG1(msg, a) logMessage(LOG_DEBUG, msg, a)
#define ERROR1(msg, a) MKL1(LOG_ERROR, msg, a) #define ERROR2(msg, a, b) logMessage(LOG_ERROR, msg, a, b)
#define WARN1(msg, a) MKL1(LOG_WARNING, msg, a) #define WARN2(msg, a, b) logMessage(LOG_WARNING, msg, a, b)
#define INFO1(msg, a) MKL1(LOG_INFO, msg, a) #define INFO2(msg, a, b) logMessage(LOG_INFO, msg, a, b)
#define DEBUG1(msg, a) MKL1(LOG_DEBUG, msg, a) #define DEBUG2(msg, a, b) logMessage(LOG_DEBUG, msg, a, b)
#define ERROR2(msg, a, b) MKL2(LOG_ERROR, msg, a, b) #define INFO3(msg, a, b, c) logMessage(LOG_INFO, msg, a, b, c)
#define WARN2(msg, a, b) MKL2(LOG_WARNING, msg, a, b)
#define INFO2(msg, a, b) MKL2(LOG_INFO, msg, a, b)
#define DEBUG2(msg, a, b) MKL2(LOG_DEBUG, msg, a, b)
#define INFO3(msg, a, b, c) MKL3(LOG_INFO, msg, a, b, c)
inline void do_free_data(rkt_data_t *d) inline void do_free_data(rkt_data_t *d)
{ {