Compare commits
11 Commits
0-1-5
...
omzetting-stdio
| Author | SHA1 | Date | |
|---|---|---|---|
| a1a56cd08c | |||
| d9af82d230 | |||
| fb1a293e88 | |||
| 803d6edd41 | |||
| 35b7bfe2e9 | |||
| c6db000d26 | |||
| 85b5a29192 | |||
| b53597396c | |||
| be980dbffd | |||
| 8165ee20cc | |||
| cacffbcc2a |
+10
-21
@@ -12,20 +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
|
||||
memqueue.h memqueue.cpp
|
||||
)
|
||||
|
||||
add_executable(rktwebview_prg
|
||||
main.cpp
|
||||
|
||||
@@ -37,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
|
||||
@@ -29,6 +29,11 @@
|
||||
#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
|
||||
{
|
||||
|
||||
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.
@@ -0,0 +1,48 @@
|
||||
[
|
||||
{
|
||||
"name": "qtbase",
|
||||
"repositories": ["qtbase", "qtactiveqt", "qtimageformats"]
|
||||
},
|
||||
{
|
||||
"name": "qtdeclarative",
|
||||
"repositories": ["qtdeclarative"]
|
||||
},
|
||||
{
|
||||
"name": "qtmultimedia",
|
||||
"repositories": ["qtmultimedia"]
|
||||
},
|
||||
{
|
||||
"name": "qtconnectivity",
|
||||
"repositories": ["qtconnectivity"]
|
||||
},
|
||||
{
|
||||
"name": "qtlocation",
|
||||
"repositories": ["qtlocation"]
|
||||
},
|
||||
{
|
||||
"name": "qtwebsockets",
|
||||
"repositories": ["qtwebsockets"]
|
||||
},
|
||||
{
|
||||
"name": "qtserialport",
|
||||
"repositories": ["qtserialport"]
|
||||
},
|
||||
{
|
||||
"name": "qtwebengine",
|
||||
"repositories": ["qtwebengine"]
|
||||
},
|
||||
{
|
||||
"name": "designer",
|
||||
"modules": ["Designer"]
|
||||
},
|
||||
{
|
||||
"name": "linguist"
|
||||
},
|
||||
{
|
||||
"name": "assistant"
|
||||
},
|
||||
{
|
||||
"name": "qt_help",
|
||||
"modules": ["Help"]
|
||||
}
|
||||
]
|
||||
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.
@@ -1,433 +1,461 @@
|
||||
#include <QThread>
|
||||
#include <QFile>
|
||||
#include <QApplication>
|
||||
|
||||
#include "rkt_protocol.h"
|
||||
#include "shm.h"
|
||||
#include "shmqueue.h"
|
||||
#include "command.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QThread>
|
||||
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "rkt_protocol.h"
|
||||
#include "rktwebview_qt.h"
|
||||
#include "utils.h"
|
||||
|
||||
static void free_data(rkt_data_t *d)
|
||||
namespace {
|
||||
|
||||
constexpr int STDIO_PROTOCOL_VERSION = 1;
|
||||
|
||||
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
|
||||
{
|
||||
public:
|
||||
Shm *shm;
|
||||
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;
|
||||
Rktwebview_qt *webviewHandler = nullptr;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int wv = data->data.event.wv;
|
||||
char *evt = data->data.event.event;
|
||||
QJsonObject message;
|
||||
message["type"] = "event";
|
||||
message["wv"] = data->data.event.wv;
|
||||
message["data"] = QString::fromUtf8(data->data.event.event);
|
||||
writeMessage(message);
|
||||
|
||||
std::string evt_d(evt);
|
||||
_handler->event_queue->enqueue(wv, evt_d);
|
||||
|
||||
free_data(data);
|
||||
freeData(data);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
const char *me = argv[0];
|
||||
INFO0("Starting stdio backend\n");
|
||||
|
||||
{
|
||||
time_t my_time = time(NULL);
|
||||
INFO1("Starting at %s", ctime(&my_time));
|
||||
}
|
||||
Handler commandHandler;
|
||||
commandHandler.webviewHandler = new Rktwebview_qt(argc, argv);
|
||||
commandHandler.webviewHandler->initApp();
|
||||
commandHandler.start();
|
||||
|
||||
if (argc < 8) {
|
||||
ERROR1("%s: wrong number of arguments\n", me);
|
||||
exit(1);
|
||||
}
|
||||
sendReady();
|
||||
commandHandler.webviewHandler->execApp();
|
||||
|
||||
const char *shm_name = argv[1];
|
||||
const char *shm_size_str = argv[2];
|
||||
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];
|
||||
INFO0("Qt event loop stopped; waiting for stdin handler\n");
|
||||
commandHandler.wait();
|
||||
|
||||
size_t shm_size = atoi(shm_size_str);
|
||||
int cmd_slot = atoi(cmd_slot_str);
|
||||
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));
|
||||
}
|
||||
delete commandHandler.webviewHandler;
|
||||
commandHandler.webviewHandler = nullptr;
|
||||
|
||||
INFO0("Exiting stdio backend\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Handler::run()
|
||||
{
|
||||
int wait_ms = 10 * 1000; // 10 seconds.
|
||||
while (!quit) {
|
||||
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;
|
||||
std::string line;
|
||||
bool quit = false;
|
||||
|
||||
while (!quit && std::getline(std::cin, line)) {
|
||||
if (line.empty()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject data_obj = QJsonDocument::fromJson(data.c_str()).object();
|
||||
|
||||
switch(cmd) {
|
||||
case CMD_NOOP: {
|
||||
if (quit) {
|
||||
DEBUG1("Alive timeout, quit = %d\n", quit);
|
||||
webview_handler->closeAllWindows();
|
||||
DEBUG0("Closed all windows\n");
|
||||
webview_handler->rktQuit();
|
||||
DEBUG0("Quit application\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CMD_QUIT: {
|
||||
INFO0("Got quit message\n");
|
||||
webview_handler->rktQuit();
|
||||
INFO0("Enqueing RESULT_QUIT to result queue\n");
|
||||
result_queue->enqueue(RESULT_QUIT);
|
||||
quit = true;
|
||||
}
|
||||
break;
|
||||
case CMD_INFO: {
|
||||
int open_windows = webview_handler->openWindows();
|
||||
result_queue->enqueue(open_windows);
|
||||
}
|
||||
break;
|
||||
case CMD_HANDLE_IS_VALID: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
bool oke = webview_handler->rktValid(wv);
|
||||
result_queue->enqueue(oke);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_LOGLEVEL: {
|
||||
int ll = data_obj["wv"].toInt();
|
||||
setLogLevel(ll);
|
||||
bool oke = true;
|
||||
result_queue->enqueue(oke);
|
||||
}
|
||||
break;
|
||||
case CMD_CONTEXT_NEW: {
|
||||
QString boilerplate_js = data_obj["boilerplate_js"].toString();
|
||||
bool has_pem_cert = data_obj["has_pem_cert"].toBool();
|
||||
QString pem_cert = data_obj["pem_cert"].toString();
|
||||
int context = webview_handler->newContext(boilerplate_js, has_pem_cert, pem_cert);
|
||||
result_queue->enqueue(context);
|
||||
}
|
||||
break;
|
||||
case CMD_CREATE_WV: {
|
||||
int context = data_obj["context"].toInt();
|
||||
int parent = data_obj["parent"].toInt();
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument document =
|
||||
QJsonDocument::fromJson(QByteArray::fromStdString(line), &parseError);
|
||||
|
||||
int wv = webview_handler->rktWebViewCreate(context, parent, event_cb);
|
||||
result_queue->enqueue(wv);
|
||||
}
|
||||
break;
|
||||
case CMD_CLOSE_WV: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
webview_handler->rktWebViewClose(wv);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_URL: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString url = data_obj["url"].toString();
|
||||
result_t r = webview_handler->rktSetUrl(wv, url.toUtf8().constData());
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_HTML: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString html = data_obj["html"].toString();
|
||||
result_t r = webview_handler->rktSetHtml(wv, html.toUtf8().constData());
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_RUN_JS: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString js = data_obj["js"].toString();
|
||||
result_t r = webview_handler->rktRunJs(wv, js);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_CALL_JS: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString js = data_obj["js"].toString();
|
||||
rkt_data_t *res = webview_handler->rktCallJs(wv, js.toUtf8().constData());
|
||||
result_queue->enqueue(res->data.js_result.result, res->data.js_result.value);
|
||||
free_data(res);
|
||||
}
|
||||
break;
|
||||
case CMD_OPEN_DEVTOOLS: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktOpenDevtools(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_MOVE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
int x = data_obj["x"].toInt();
|
||||
int y = data_obj["y"].toInt();
|
||||
result_t r = webview_handler->rktMove(wv, x, y);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_RESIZE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
int w = data_obj["w"].toInt();
|
||||
int h = data_obj["h"].toInt();
|
||||
result_t r = webview_handler->rktResize(wv, w, h);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_HIDE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktHideWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SHOW: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktShowWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_PRESENT: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktPresentWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_MAXIMIZE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktMaximizeWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_MINIMIZE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktMinimizeWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SHOW_NORMAL: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
result_t r = webview_handler->rktShowNormalWindow(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_WINDOW_STATE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
window_state_t r = webview_handler->rktWindowState(wv);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_TITLE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString title = data_obj["title"].toString();
|
||||
result_t r = webview_handler->rktWindowSetTitle(wv, title.toUtf8().constData());
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_ICON: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString icon_file = data_obj["icon"].toString();
|
||||
result_t r = webview_handler->rktWindowSetIcon(wv, icon_file.toUtf8().constData());
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_CHOOSE_DIR: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString title = data_obj["title"].toString();
|
||||
QString base_dir = data_obj["base_dir"].toString();
|
||||
result_t r = webview_handler->rktChooseDir(wv,
|
||||
title.toUtf8().constData(),
|
||||
base_dir.toUtf8().constData()
|
||||
);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_FILE_OPEN: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString title = data_obj["title"].toString();
|
||||
QString base_dir = data_obj["base_dir"].toString();
|
||||
QString permitted_exts = data_obj["permitted_exts"].toString();
|
||||
result_t r = webview_handler->rktFileOpen(wv,
|
||||
title.toUtf8().constData(),
|
||||
base_dir.toUtf8().constData(),
|
||||
permitted_exts.toUtf8().constData()
|
||||
);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_FILE_SAVE: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString title = data_obj["title"].toString();
|
||||
QString base_dir = data_obj["base_dir"].toString();
|
||||
QString permitted_exts = data_obj["permitted_exts"].toString();
|
||||
result_t r = webview_handler->rktFileSave(wv,
|
||||
title.toUtf8().constData(),
|
||||
base_dir.toUtf8().constData(),
|
||||
permitted_exts.toUtf8().constData()
|
||||
);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
case CMD_SET_OU_TOKEN: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString token = data_obj["token"].toString();
|
||||
webview_handler->rktSetOUToken(wv, token.toUtf8().constData());
|
||||
}
|
||||
break;
|
||||
case CMD_MSG_BOX: {
|
||||
int wv = data_obj["wv"].toInt();
|
||||
QString title = data_obj["title"].toString();
|
||||
QString message = data_obj["message"].toString();
|
||||
QString submsg = data_obj["submessage"].toString();
|
||||
int type = data_obj["type"].toInt();
|
||||
result_t r = webview_handler->rktMessageBox(wv,
|
||||
title.toUtf8().constData(),
|
||||
message.toUtf8().constData(),
|
||||
submsg.toUtf8().constData(),
|
||||
static_cast<rkt_messagetype_t>(type)
|
||||
);
|
||||
result_queue->enqueue(r);
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
ERROR1("Unknown command: %d\n", cmd);
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
DEBUG0("Exiting handler thread\n");
|
||||
}
|
||||
|
||||
void Alive::run()
|
||||
{
|
||||
int wait_ms = 10 * 1000;
|
||||
int ping_no;
|
||||
std::string data;
|
||||
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;
|
||||
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;
|
||||
|
||||
case CMD_QUIT:
|
||||
INFO0("Got quit command\n");
|
||||
webviewHandler->rktQuit();
|
||||
sendResult(id, RESULT_QUIT);
|
||||
quit = true;
|
||||
break;
|
||||
|
||||
case CMD_INFO:
|
||||
sendResult(id, webviewHandler->openWindows());
|
||||
break;
|
||||
|
||||
case CMD_HANDLE_IS_VALID: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktValid(wv) ? 1 : 0);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_LOGLEVEL: {
|
||||
const int level = data["wv"].toInt();
|
||||
setLogLevel(level);
|
||||
sendResult(id, result_t::oke);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CONTEXT_NEW: {
|
||||
const QString boilerplateJs = data["boilerplate_js"].toString();
|
||||
const bool hasPemCertificate = data["has_pem_cert"].toBool();
|
||||
const QString pemCertificate = data["pem_cert"].toString();
|
||||
const int context = webviewHandler->newContext(boilerplateJs,
|
||||
hasPemCertificate,
|
||||
pemCertificate);
|
||||
sendResult(id, context);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CREATE_WV: {
|
||||
const int context = data["context"].toInt();
|
||||
const int parent = data["parent"].toInt();
|
||||
const int wv = webviewHandler->rktWebViewCreate(context,
|
||||
parent,
|
||||
eventCallback);
|
||||
sendResult(id, wv);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CLOSE_WV: {
|
||||
const int wv = data["wv"].toInt();
|
||||
webviewHandler->rktWebViewClose(wv);
|
||||
sendResult(id, result_t::oke);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_URL: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString url = data["url"].toString();
|
||||
sendResult(id, webviewHandler->rktSetUrl(wv, url.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_HTML: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString html = data["html"].toString();
|
||||
sendResult(id, webviewHandler->rktSetHtml(wv, html.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_RUN_JS: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString js = data["js"].toString();
|
||||
sendResult(id, webviewHandler->rktRunJs(wv, js));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CALL_JS: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString js = data["js"].toString();
|
||||
rkt_data_t *response = webviewHandler->rktCallJs(wv, js.toUtf8().constData());
|
||||
if (response == nullptr || response->kind != rkt_data_kind_t::js_result) {
|
||||
sendResult(id, result_t::failed, "Missing JavaScript result");
|
||||
} else {
|
||||
DEBUG1("Got alive ping: %d\n", ping_no);
|
||||
alive_ack_queue->enqueue(ping_no);
|
||||
sendResult(id,
|
||||
response->data.js_result.result,
|
||||
QString::fromUtf8(response->data.js_result.value));
|
||||
}
|
||||
freeData(response);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_OPEN_DEVTOOLS: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktOpenDevtools(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_MOVE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id,
|
||||
webviewHandler->rktMove(wv,
|
||||
data["x"].toInt(),
|
||||
data["y"].toInt()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_RESIZE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id,
|
||||
webviewHandler->rktResize(wv,
|
||||
data["w"].toInt(),
|
||||
data["h"].toInt()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_HIDE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktHideWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SHOW: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktShowWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_PRESENT: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktPresentWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_MAXIMIZE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktMaximizeWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_MINIMIZE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktMinimizeWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SHOW_NORMAL: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktShowNormalWindow(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_WINDOW_STATE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktWindowState(wv));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_TITLE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktWindowSetTitle(wv,
|
||||
title.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_ICON: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString icon = data["icon"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktWindowSetIcon(wv,
|
||||
icon.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CREATE_TRAY: {
|
||||
const QString icon = data["icon"].toString();
|
||||
const QString tooltip = data["tooltip"].toString();
|
||||
const int tray = webviewHandler->rktTrayCreate(icon.toUtf8().constData(),
|
||||
tooltip.toUtf8().constData(),
|
||||
eventCallback);
|
||||
sendResult(id, tray);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRAY_SET_ICON: {
|
||||
const int tray = data["wv"].toInt();
|
||||
const QString icon = data["icon"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktTraySetIcon(tray,
|
||||
icon.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRAY_SET_TOOLTIP: {
|
||||
const int tray = data["wv"].toInt();
|
||||
const QString tooltip = data["tooltip"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktTraySetTooltip(tray,
|
||||
tooltip.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRAY_SHOW_MESSAGE: {
|
||||
const int tray = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
const QString message = data["message"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktTrayShowMessage(tray,
|
||||
title.toUtf8().constData(),
|
||||
message.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRAY_CLEAR_MENU: {
|
||||
const int tray = data["wv"].toInt();
|
||||
sendResult(id, webviewHandler->rktTraySetMenu(tray, nullptr));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_TRAY_SET_MENU: {
|
||||
const int tray = data["wv"].toInt();
|
||||
const QString menuJson = data["menu_json"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktTraySetMenu(tray,
|
||||
menuJson.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_CHOOSE_DIR: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
const QString baseDir = data["base_dir"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktChooseDir(wv,
|
||||
title.toUtf8().constData(),
|
||||
baseDir.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_FILE_OPEN: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
const QString baseDir = data["base_dir"].toString();
|
||||
const QString permittedExtensions = data["permitted_exts"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktFileOpen(wv,
|
||||
title.toUtf8().constData(),
|
||||
baseDir.toUtf8().constData(),
|
||||
permittedExtensions.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_FILE_SAVE: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
const QString baseDir = data["base_dir"].toString();
|
||||
const QString permittedExtensions = data["permitted_exts"].toString();
|
||||
sendResult(id,
|
||||
webviewHandler->rktFileSave(wv,
|
||||
title.toUtf8().constData(),
|
||||
baseDir.toUtf8().constData(),
|
||||
permittedExtensions.toUtf8().constData()));
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_SET_OU_TOKEN: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString token = data["token"].toString();
|
||||
webviewHandler->rktSetOUToken(wv, token.toUtf8().constData());
|
||||
sendResult(id, result_t::oke);
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_MSG_BOX: {
|
||||
const int wv = data["wv"].toInt();
|
||||
const QString title = data["title"].toString();
|
||||
const QString message = data["message"].toString();
|
||||
const QString submessage = data["submessage"].toString();
|
||||
const auto type = static_cast<rkt_messagetype_t>(data["type"].toInt());
|
||||
sendResult(id,
|
||||
webviewHandler->rktMessageBox(wv,
|
||||
title.toUtf8().constData(),
|
||||
message.toUtf8().constData(),
|
||||
submessage.toUtf8().constData(),
|
||||
type));
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
const QString message = QString("Unknown command: %1").arg(command);
|
||||
ERROR1("%s\n", message.toUtf8().constData());
|
||||
sendResult(id, result_t::failed, message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
DEBUG0("Exiting alive thread\n");
|
||||
|
||||
if (!quit) {
|
||||
INFO0("stdin closed; stopping Qt backend\n");
|
||||
webviewHandler->closeAllWindows();
|
||||
webviewHandler->rktQuit();
|
||||
}
|
||||
|
||||
DEBUG0("Exiting stdin handler\n");
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
@@ -1,6 +1,10 @@
|
||||
#ifndef 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_QUIT 2
|
||||
#define CMD_CONTEXT_NEW 3 // arguments: boilerplate_js: string, has_cert: bool, cert_pem: string -> context: int
|
||||
@@ -29,6 +33,13 @@
|
||||
#define CMD_SET_LOGLEVEL 26 // arguments: ll: int
|
||||
#define CMD_INFO 27 // arguments: none
|
||||
#define CMD_SET_ICON 28 // arguments: wv: int, icon:string (absolute filename to icon in png/jpg/svg)
|
||||
#define CMD_CREATE_TRAY 29 // arguments: icon: string (path to icon file), tooltip: string -> tray: int
|
||||
#define CMD_TRAY_SET_ICON 30 // arguments: tray: int, icon: string -> result_t: int
|
||||
#define CMD_TRAY_SET_TOOLTIP 31 // arguments: tray: int, tooltip: string -> result_t: int
|
||||
#define CMD_TRAY_SHOW_MESSAGE 32 // arguments: tray: int, title: string, message: string -> result_t: int
|
||||
#define CMD_TRAY_SET_MENU 33 // arguments: tray: int, menu_json: string -> result_t: int
|
||||
#define CMD_TRAY_CLEAR_MENU 34 // arguments: tray: int
|
||||
|
||||
|
||||
#define CMD_NOOP 33621
|
||||
#define RESULT_QUIT 36379
|
||||
|
||||
+124
-28
@@ -38,8 +38,6 @@
|
||||
#define sleep_ms(ms) Sleep(static_cast<DWORD>(ms))
|
||||
#endif
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#ifdef __linux
|
||||
static int sleep_ms(long msec)
|
||||
{
|
||||
@@ -232,7 +230,12 @@ bool runRktWebview(Handle_t *handler)
|
||||
#define WAIT_ON_EVENT_MS (2 * 1000)
|
||||
#define ALIVE_MESSAGE_INTERVAL_S 5 // Should be smaller than 10 seconds
|
||||
#define MAX_WAIT_RESULT (2 * 1000) // Maximum wait in milliseconds for a result per time
|
||||
|
||||
#ifdef DEBUG_RKT_WEBVIEW
|
||||
#define MAX_ALIVE_ACK_TIME (300 * 1000)
|
||||
#else
|
||||
#define MAX_ALIVE_ACK_TIME (10 * 1000)
|
||||
#endif
|
||||
|
||||
void rkt_evt_guard(void)
|
||||
{
|
||||
@@ -248,10 +251,9 @@ void rkt_evt_guard(void)
|
||||
handler->evt_queue->enqueue(wv, data);
|
||||
handler->evt_cb_mutex.lock();
|
||||
if (handler->evt_cb != nullptr) { // evt_cb could be cleared to null
|
||||
INFO2("Calling evt_cb with %d, %s\n", wv, data.c_str());
|
||||
|
||||
//INFO2("Calling evt_cb with %d, %s\n", wv, data.c_str());
|
||||
handler->evt_cb(1);
|
||||
INFO0("done\n");
|
||||
//INFO0("done\n");
|
||||
} else {
|
||||
INFO0("evt_cb = null\n");
|
||||
}
|
||||
@@ -397,7 +399,7 @@ void rkt_webview_init(const char *from)
|
||||
INFO1("rkt_webview_init called from %s\n", from);
|
||||
|
||||
char buf[1024];
|
||||
#ifdef DEBUG
|
||||
#ifdef DEBUG_RKT_WEBVIEW
|
||||
sprintf(buf, "rktwebview-dbg");
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
@@ -427,7 +429,7 @@ void rkt_webview_init(const char *from)
|
||||
handler->alive_ack_queue = new ShmQueue(handler->shm, ALIVE_ACK_SLOT, true);
|
||||
|
||||
// Start rktwebview_prg application with the right information
|
||||
#ifndef DEBUG
|
||||
#ifndef DEBUG_RKT_WEBVIEW
|
||||
handler->rkt_webview_prg_started = runRktWebview(handler);
|
||||
if (!handler->rkt_webview_prg_started) { handler->valid = false; }
|
||||
#endif
|
||||
@@ -552,28 +554,70 @@ void rkt_webview_close(rktwebview_t wv)
|
||||
handler->command_queue->enqueue(CMD_CLOSE_WV, j.dump());
|
||||
}
|
||||
|
||||
#define CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, key4, val4) \
|
||||
RKT_WEBVIEW_INIT; \
|
||||
FAIL_HANDLE \
|
||||
JSON j; \
|
||||
j["wv"] = wv; \
|
||||
j[key] = val; \
|
||||
j[key2] = val2; \
|
||||
j[key3] = val3; \
|
||||
j[key4] = val4; \
|
||||
handler->command_queue->enqueue(cmd, j.dump()); \
|
||||
int result; \
|
||||
std::string json_result; \
|
||||
while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) { \
|
||||
if (handler->alive_error) { \
|
||||
return result_t::failed; \
|
||||
} \
|
||||
} \
|
||||
result_t r = static_cast<result_t>(result); \
|
||||
static inline result_t DO_CMDRES(int cmd, rktwebview_t wv, const JSON &j)
|
||||
{
|
||||
RKT_WEBVIEW_INIT;
|
||||
FAIL_HANDLE
|
||||
std::string json_str = j.dump();
|
||||
handler->command_queue->enqueue(cmd, json_str);
|
||||
int result;
|
||||
std::string json_result;
|
||||
while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
|
||||
if (handler->alive_error) {
|
||||
return result_t::failed;
|
||||
}
|
||||
}
|
||||
result_t r = static_cast<result_t>(result);
|
||||
return r;
|
||||
}
|
||||
|
||||
static inline result_t CMDRES4(int cmd, rktwebview_t wv,
|
||||
const char *key, const char *val,
|
||||
const char *key2, const char *val2,
|
||||
const char *key3, const char *val3,
|
||||
const char *key4, const char *val4)
|
||||
{
|
||||
JSON j;
|
||||
j["wv"] = wv;
|
||||
j[key] = val;
|
||||
j[key2] = val2;
|
||||
j[key3] = val3;
|
||||
j[key4] = val4;
|
||||
return DO_CMDRES(cmd, wv, j);
|
||||
}
|
||||
|
||||
static inline result_t CMDRES4(int cmd, rktwebview_t wv,
|
||||
const char *key, int val,
|
||||
const char *key2, int val2,
|
||||
const char *key3, const char *val3,
|
||||
const char *key4, const char *val4)
|
||||
{
|
||||
JSON j;
|
||||
j["wv"] = wv;
|
||||
j[key] = val;
|
||||
j[key2] = val2;
|
||||
j[key3] = val3;
|
||||
j[key4] = val4;
|
||||
return DO_CMDRES(cmd, wv, j);
|
||||
}
|
||||
|
||||
static inline result_t CMDRES4(int cmd, rktwebview_t wv,
|
||||
const char *key, const char *val,
|
||||
const char *key2, const char *val2,
|
||||
const char *key3, const char *val3,
|
||||
const char *key4, int val4)
|
||||
{
|
||||
JSON j;
|
||||
j["wv"] = wv;
|
||||
j[key] = val;
|
||||
j[key2] = val2;
|
||||
j[key3] = val3;
|
||||
j[key4] = val4;
|
||||
return DO_CMDRES(cmd, wv, j);
|
||||
}
|
||||
|
||||
#define CMDRES3(cmd, wv, key, val, key2, val2, key3, val3) \
|
||||
CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, "nil3", "none")
|
||||
return CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, "nil3", "none");
|
||||
|
||||
#define CMDRES2(cmd, wv, key, val, key2, val2) \
|
||||
CMDRES3(cmd, wv, key, val, key2, val2, "nil2", "none")
|
||||
@@ -586,7 +630,7 @@ void rkt_webview_close(rktwebview_t wv)
|
||||
|
||||
result_t rkt_webview_set_url(rktwebview_t wv, const char *url)
|
||||
{
|
||||
CMDRES(CMD_SET_URL, wv, "url", url)
|
||||
CMDRES(CMD_SET_URL, wv, "url", url);
|
||||
}
|
||||
|
||||
result_t rkt_webview_set_html(rktwebview_t wv, const char *html)
|
||||
@@ -765,7 +809,7 @@ rkt_data_t *rkt_webview_version()
|
||||
|
||||
result_t rkt_webview_message_box(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type)
|
||||
{
|
||||
CMDRES4(CMD_MSG_BOX, w, "title", title, "message", message, "submessage", submessage, "type", static_cast<int>(type))
|
||||
return CMDRES4(CMD_MSG_BOX, w, "title", title, "message", message, "submessage", submessage, "type", static_cast<int>(type));
|
||||
}
|
||||
|
||||
int rkt_webview_events_waiting()
|
||||
@@ -877,3 +921,55 @@ void rkt_webview_exit_done(int done)
|
||||
ERROR0("rkt_webview_exit_done called with 'false', i.e. this library did not have a cleanup call\n");
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
// Tray specific functions
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
rktwebview_t rkt_webview_tray_create(const char *icon_file,
|
||||
const char *tooltip)
|
||||
{
|
||||
RKT_WEBVIEW_INIT;
|
||||
FAIL_WV
|
||||
|
||||
JSON j;
|
||||
j["icon"] = icon_file;
|
||||
j["tooltip"] = tooltip;
|
||||
|
||||
handler->command_queue->enqueue(CMD_CREATE_TRAY, j.dump());
|
||||
|
||||
int result;
|
||||
std::string json_result;
|
||||
while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
|
||||
if (handler->alive_error) {
|
||||
return result_t::failed;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
result_t rkt_webview_tray_set_icon(rktwebview_t tray, const char *icon_file)
|
||||
{
|
||||
CMDRES(CMD_TRAY_SET_ICON, tray, "icon", icon_file)
|
||||
}
|
||||
|
||||
result_t rkt_webview_tray_set_tooltip(rktwebview_t tray, const char *tooltip)
|
||||
{
|
||||
CMDRES(CMD_TRAY_SET_TOOLTIP, tray, "tooltip", tooltip)
|
||||
}
|
||||
|
||||
result_t rkt_webview_tray_show_message(rktwebview_t tray, const char *title, const char *message)
|
||||
{
|
||||
CMDRES2(CMD_TRAY_SHOW_MESSAGE, tray, "title", title, "message", message)
|
||||
}
|
||||
|
||||
result_t rkt_webview_tray_set_menu(rktwebview_t tray, const char *menu_json)
|
||||
{
|
||||
if (menu_json == nullptr) {
|
||||
CMDRES0(CMD_TRAY_CLEAR_MENU, tray)
|
||||
} else {
|
||||
CMDRES(CMD_TRAY_SET_MENU, tray, "menu_json", menu_json)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,13 @@ RKTWEBVIEW_EXPORT result_t rkt_webview_file_save(rktwebview_t w, const char *tit
|
||||
|
||||
RKTWEBVIEW_EXPORT result_t rkt_webview_message_box(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type);
|
||||
|
||||
// Tray Icon functions (note, also hide/show/close can be used).
|
||||
RKTWEBVIEW_EXPORT rktwebview_t rkt_webview_tray_create(const char *icon_file, const char *tooltip);
|
||||
RKTWEBVIEW_EXPORT result_t rkt_webview_tray_set_icon(rktwebview_t tray, const char *icon_file);
|
||||
RKTWEBVIEW_EXPORT result_t rkt_webview_tray_set_tooltip(rktwebview_t tray, const char *tooltip);
|
||||
RKTWEBVIEW_EXPORT result_t rkt_webview_tray_show_message(rktwebview_t tray, const char *title, const char *message);
|
||||
RKTWEBVIEW_EXPORT result_t rkt_webview_tray_set_menu(rktwebview_t tray, const char *menu_json);
|
||||
|
||||
}
|
||||
|
||||
#endif // RKTWEBVIEW_H
|
||||
|
||||
+292
-7
@@ -18,15 +18,36 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QAbstractButton>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include "build_menu_from_json.h"
|
||||
|
||||
static inline char *copyString(const char *s)
|
||||
{
|
||||
int l = strlen(s);
|
||||
int l = static_cast<int>(strlen(s));
|
||||
char *cpy = static_cast<char *>(malloc(l + 1));
|
||||
memcpy(cpy, s, l + 1);
|
||||
return cpy;
|
||||
}
|
||||
|
||||
static QString activationReasonToString(QSystemTrayIcon::ActivationReason r)
|
||||
{
|
||||
switch (r) {
|
||||
case QSystemTrayIcon::Context:
|
||||
return "context-menu"; // rechtsklik / menu openen
|
||||
case QSystemTrayIcon::DoubleClick:
|
||||
return "double-click";
|
||||
case QSystemTrayIcon::Trigger:
|
||||
return "click"; // meestal linkerklik
|
||||
case QSystemTrayIcon::MiddleClick:
|
||||
return "middle-click";
|
||||
case QSystemTrayIcon::Unknown:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void Rktwebview_qt::processCommand(Command *cmd)
|
||||
{
|
||||
switch(cmd->cmd) {
|
||||
@@ -159,15 +180,28 @@ void Rktwebview_qt::processCommand(Command *cmd)
|
||||
case COMMAND_CLOSE: {
|
||||
int wv = cmd->args[0].toInt();
|
||||
if (_views.contains(wv)) {
|
||||
WebviewWindow *w= _views[wv];
|
||||
_views.remove(wv);
|
||||
WebviewWindow *w = _views[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();
|
||||
cmd->result = true;
|
||||
while(w->isVisible()) {
|
||||
_app->processEvents();
|
||||
} else if (_trays.contains(wv)) {
|
||||
QSystemTrayIcon *tray = _trays[wv];
|
||||
_trays.remove(wv);
|
||||
|
||||
if (_tray_menus.contains(wv)) {
|
||||
QMenu *menu = _tray_menus[wv];
|
||||
_tray_menus.remove(wv);
|
||||
delete menu;
|
||||
}
|
||||
_view_js_callbacks.remove(wv);
|
||||
delete w;
|
||||
|
||||
tray->hide();
|
||||
_view_js_callbacks.remove(wv);
|
||||
delete tray;
|
||||
cmd->result = true;
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
@@ -306,6 +340,18 @@ void Rktwebview_qt::processCommand(Command *cmd)
|
||||
w->activateWindow();
|
||||
}
|
||||
cmd->result = true;
|
||||
} else if (_trays.contains(wv)) {
|
||||
QSystemTrayIcon *tray = _trays[wv];
|
||||
|
||||
if (cmd->cmd == COMMAND_SHOW_WIN) {
|
||||
tray->show();
|
||||
cmd->result = true;
|
||||
} else if (cmd->cmd == COMMAND_HIDE_WIN) {
|
||||
tray->hide();
|
||||
cmd->result = true;
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
@@ -449,6 +495,118 @@ void Rktwebview_qt::processCommand(Command *cmd)
|
||||
}
|
||||
cmd->done = true;
|
||||
}
|
||||
case COMMAND_CREATE_TRAY: {
|
||||
QString icon_file = cmd->args[0].toString();
|
||||
QString tooltip = cmd->args[1].toString();
|
||||
|
||||
void *f = cmd->args[2].value<void *>();
|
||||
event_cb_t js_event_cb = reinterpret_cast <event_cb_t>(f);
|
||||
|
||||
int id = nextHandle();
|
||||
|
||||
QSystemTrayIcon *tray = new QSystemTrayIcon(this);
|
||||
tray->setIcon(QIcon(icon_file));
|
||||
tray->setToolTip(tooltip);
|
||||
tray->setProperty("tray-id", id);
|
||||
|
||||
connect(tray, &QSystemTrayIcon::activated, this, &Rktwebview_qt::handleTrayActivation);
|
||||
connect(tray, &QSystemTrayIcon::messageClicked, this, &Rktwebview_qt::handleTrayMessageClick);
|
||||
|
||||
_trays[id] = tray;
|
||||
_view_js_callbacks[id] = js_event_cb;
|
||||
|
||||
tray->show();
|
||||
|
||||
cmd->result = id;
|
||||
cmd->done = true;
|
||||
}
|
||||
break;
|
||||
case COMMAND_TRAY_SET_ICON: {
|
||||
int tray_id = cmd->args[0].toInt();
|
||||
QString icon_file = cmd->args[1].toString();
|
||||
|
||||
if (_trays.contains(tray_id)) {
|
||||
_trays[tray_id]->setIcon(QIcon(icon_file));
|
||||
cmd->result = true;
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
|
||||
cmd->done = true;
|
||||
}
|
||||
break;
|
||||
case COMMAND_TRAY_SET_TOOLTIP: {
|
||||
int tray_id = cmd->args[0].toInt();
|
||||
QString tooltip = cmd->args[1].toString();
|
||||
|
||||
if (_trays.contains(tray_id)) {
|
||||
_trays[tray_id]->setToolTip(tooltip);
|
||||
cmd->result = true;
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
|
||||
cmd->done = true;
|
||||
}
|
||||
break;
|
||||
case COMMAND_TRAY_SHOW_MESSAGE: {
|
||||
int tray_id = cmd->args[0].toInt();
|
||||
QString title = cmd->args[1].toString();
|
||||
QString message = cmd->args[2].toString();
|
||||
|
||||
if (_trays.contains(tray_id)) {
|
||||
_trays[tray_id]->showMessage(title, message);
|
||||
cmd->result = true;
|
||||
} else {
|
||||
cmd->result = false;
|
||||
}
|
||||
|
||||
cmd->done = true;
|
||||
}
|
||||
break;
|
||||
case COMMAND_TRAY_SET_MENU: {
|
||||
int tray_id = cmd->args[0].toInt();
|
||||
int has_menu = cmd->args[1].toInt();
|
||||
QString menu_json = cmd->args[2].toString();
|
||||
|
||||
if (!_trays.contains(tray_id)) {
|
||||
cmd->result = false;
|
||||
cmd->done = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (_tray_menus.contains(tray_id)) {
|
||||
QMenu *old = _tray_menus[tray_id];
|
||||
_tray_menus.remove(tray_id);
|
||||
delete old;
|
||||
}
|
||||
|
||||
QMenu *menu = (has_menu) ? buildMenuFromJson(this, menu_json, tray_id) : nullptr;
|
||||
_tray_menus[tray_id] = menu;
|
||||
|
||||
/*
|
||||
if (menu == nullptr) {
|
||||
if (!has_menu) {
|
||||
INFO1("null menu, setting null menu on tray %d\n", tray_id);
|
||||
_trays[tray_id]->setContextMenu(nullptr);
|
||||
INFO0("Done setting tray menu to null menu\n");
|
||||
WARN0("Note: on Linux, this will not clear the context menu\n");
|
||||
cmd->result = true;
|
||||
cmd->done = true;
|
||||
} else {
|
||||
ERROR2("menu json '%s' for tray %d is not oke\n", menu_json.toUtf8().constData(), tray_id);
|
||||
cmd->result = false;
|
||||
cmd->done = true;
|
||||
}
|
||||
} else {
|
||||
_tray_menus[tray_id] = menu;
|
||||
_trays[tray_id]->setContextMenu(menu);
|
||||
}
|
||||
*/
|
||||
|
||||
cmd->result = true;
|
||||
cmd->done = true;
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
cmd->result = false;
|
||||
@@ -458,6 +616,50 @@ void Rktwebview_qt::processCommand(Command *cmd)
|
||||
}
|
||||
}
|
||||
|
||||
void Rktwebview_qt::handleTrayActivation(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
QObject *obj = sender();
|
||||
int tray_wv = obj->property("tray-id").toInt();
|
||||
|
||||
QSystemTrayIcon *icn = qobject_cast<QSystemTrayIcon *>(obj);
|
||||
|
||||
// If a context menu has been set, pop it up and leave.
|
||||
if (reason == QSystemTrayIcon::ActivationReason::Context) {
|
||||
QMenu *mnu = nullptr;
|
||||
if (_tray_menus.contains(tray_wv)) {
|
||||
mnu = _tray_menus[tray_wv];
|
||||
}
|
||||
if (mnu != nullptr) {
|
||||
mnu->popup(QCursor::pos());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QString evt = "tray-activated";
|
||||
QString reason_str = activationReasonToString(reason);
|
||||
|
||||
//INFO3("triggering event: %s for tray %d with reason %s\n",
|
||||
// evt.toUtf8().constData(), tray_wv, reason_str.toUtf8().constData());
|
||||
|
||||
EventContainer e(evt);
|
||||
e["reason"] = reason_str;
|
||||
|
||||
this->triggerEvent(tray_wv, e);
|
||||
}
|
||||
|
||||
void Rktwebview_qt::handleTrayMessageClick()
|
||||
{
|
||||
QObject *obj = sender();
|
||||
|
||||
//INFO0("triggering message click\n");
|
||||
|
||||
int tray_wv = obj->property("tray-id").toInt();
|
||||
QString evt = "tray-message-clicked";
|
||||
|
||||
EventContainer e(evt);
|
||||
this->triggerEvent(tray_wv, e);
|
||||
}
|
||||
|
||||
void Rktwebview_qt::removeView(int id)
|
||||
{
|
||||
if (_views.contains(id)) {
|
||||
@@ -888,6 +1090,76 @@ result_t Rktwebview_qt::rktMessageBox(rktwebview_t w, const char *title, const c
|
||||
return r ? result_t::oke : result_t::failed;
|
||||
}
|
||||
|
||||
rktwebview_t Rktwebview_qt::rktTrayCreate(const char *icon_file, const char *tooltip, event_cb_t evt_cb)
|
||||
{
|
||||
Command c(COMMAND_CREATE_TRAY);
|
||||
c.args.push_back(icon_file);
|
||||
c.args.push_back(tooltip);
|
||||
|
||||
void *function = reinterpret_cast<void *>(evt_cb);
|
||||
QVariant f(QVariant::fromValue(function));
|
||||
c.args.push_back(f);
|
||||
|
||||
postCommand(&c);
|
||||
while(!c.done) { doEvents(); }
|
||||
|
||||
return c.result.toInt();
|
||||
}
|
||||
|
||||
result_t Rktwebview_qt::rktTraySetIcon(rktwebview_t tray, const char *icon_file)
|
||||
{
|
||||
Command c(COMMAND_TRAY_SET_ICON);
|
||||
c.args.push_back(tray);
|
||||
c.args.push_back(icon_file);
|
||||
|
||||
postCommand(&c);
|
||||
while(!c.done) { doEvents(); }
|
||||
|
||||
return c.result.toBool() ? result_t::oke : result_t::failed;
|
||||
}
|
||||
|
||||
result_t Rktwebview_qt::rktTraySetTooltip(rktwebview_t tray, const char *tooltip)
|
||||
{
|
||||
//INFO2("rktTraySetTooltip: %d %s\n", tray, tooltip);
|
||||
|
||||
Command c(COMMAND_TRAY_SET_TOOLTIP);
|
||||
c.args.push_back(tray);
|
||||
c.args.push_back(tooltip);
|
||||
|
||||
postCommand(&c);
|
||||
while(!c.done) { doEvents(); }
|
||||
|
||||
//INFO1("rktTraySetTooltip done: %s\n", tooltip);
|
||||
|
||||
return c.result.toBool() ? result_t::oke : result_t::failed;
|
||||
}
|
||||
|
||||
result_t Rktwebview_qt::rktTrayShowMessage(rktwebview_t tray, const char *title, const char *message)
|
||||
{
|
||||
Command c(COMMAND_TRAY_SHOW_MESSAGE);
|
||||
c.args.push_back(tray);
|
||||
c.args.push_back(title);
|
||||
c.args.push_back(message);
|
||||
|
||||
postCommand(&c);
|
||||
while(!c.done) { doEvents(); }
|
||||
|
||||
return c.result.toBool() ? result_t::oke : result_t::failed;
|
||||
}
|
||||
|
||||
result_t Rktwebview_qt::rktTraySetMenu(rktwebview_t tray, const char *menu_json)
|
||||
{
|
||||
Command c(COMMAND_TRAY_SET_MENU);
|
||||
c.args.push_back(tray);
|
||||
c.args.push_back(menu_json != nullptr);
|
||||
c.args.push_back((menu_json == nullptr) ? "" : menu_json);
|
||||
|
||||
postCommand(&c);
|
||||
while(!c.done) { doEvents(); }
|
||||
|
||||
return c.result.toBool() ? result_t::oke : result_t::failed;
|
||||
}
|
||||
|
||||
result_t Rktwebview_qt::doWindow(rktwebview_t w, int cmd, result_t on_error)
|
||||
{
|
||||
Command c(cmd);
|
||||
@@ -1085,5 +1357,18 @@ Rktwebview_qt::~Rktwebview_qt()
|
||||
delete p;
|
||||
}
|
||||
|
||||
QList<int> tray_keys = _trays.keys();
|
||||
for(i = 0, N = tray_keys.size(); i < N; i++) {
|
||||
QSystemTrayIcon *tray = _trays[tray_keys[i]];
|
||||
tray->hide();
|
||||
delete tray;
|
||||
}
|
||||
|
||||
QList<int> menu_keys = _tray_menus.keys();
|
||||
for(i = 0, N = menu_keys.size(); i < N; i++) {
|
||||
QMenu *menu = _tray_menus[menu_keys[i]];
|
||||
delete menu;
|
||||
}
|
||||
|
||||
delete _app;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,15 @@
|
||||
#include <QTimer>
|
||||
#include <QEventLoop>
|
||||
#include <QFileDialog>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
|
||||
class WebViewQt;
|
||||
class WebviewWindow;
|
||||
class Command;
|
||||
class CommandEvent;
|
||||
class QSystemTrayIcon;
|
||||
class QMenu;
|
||||
|
||||
class Rktwebview_qt : public QObject
|
||||
{
|
||||
@@ -30,6 +33,8 @@ private:
|
||||
int _context_counter;
|
||||
QHash<int, QWebEngineProfile *> _contexts;
|
||||
QHash<int, WebviewWindow *> _views;
|
||||
QHash<int, QSystemTrayIcon *> _trays;
|
||||
QHash<int, QMenu *> _tray_menus;
|
||||
QHash<int, event_cb_t> _view_js_callbacks;
|
||||
|
||||
QTimer _process_events;
|
||||
@@ -46,6 +51,8 @@ private:
|
||||
|
||||
private slots:
|
||||
void stopEventloop();
|
||||
void handleTrayActivation(QSystemTrayIcon::ActivationReason reason);
|
||||
void handleTrayMessageClick();
|
||||
|
||||
private:
|
||||
void postCommand(Command *cmd);
|
||||
@@ -114,6 +121,13 @@ public:
|
||||
|
||||
bool rktValid(rktwebview_t wv);
|
||||
|
||||
public: // Tray
|
||||
rktwebview_t rktTrayCreate(const char *icon_file, const char *tooltip, event_cb_t evt_cb);
|
||||
result_t rktTraySetIcon(rktwebview_t tray, const char *icon_file);
|
||||
result_t rktTraySetTooltip(rktwebview_t tray, const char *tooltip);
|
||||
result_t rktTrayShowMessage(rktwebview_t tray, const char *title, const char *message);
|
||||
result_t rktTraySetMenu(rktwebview_t tray, const char *menu_json);
|
||||
|
||||
public:
|
||||
// Events for the backend
|
||||
void pageLoaded(rktwebview_t w, bool ok);
|
||||
|
||||
+286
-62
@@ -1,77 +1,301 @@
|
||||
#include "rktwebview.h"
|
||||
#include <string>
|
||||
#include <QCoreApplication>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFileInfo>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QProcess>
|
||||
#include <QStringList>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#include "utils.h"
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
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[])
|
||||
{
|
||||
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);
|
||||
#ifdef _WIN32
|
||||
std::string prg = loc + "\\rktwebview_prg.exe";
|
||||
SetDllDirectoryA("C:\\Qt\\6.10.2\\msvc2022_64\\bin");
|
||||
#else
|
||||
std::string prg = loc + "/rktwebview_prg";
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
{
|
||||
std::string e = std::string("RKT_WEBVIEW_PRG=") + prg;
|
||||
_putenv(e.c_str());
|
||||
if (!QFileInfo::exists(program)) {
|
||||
std::fprintf(stderr,
|
||||
"FAIL: backend executable not found: %s\n",
|
||||
program.toUtf8().constData());
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
setenv("RKT_WEBVIEW_PRG", prg.c_str(), true);
|
||||
setenv("LD_LIBRARY_PATH", loc.c_str(), true);
|
||||
#endif
|
||||
|
||||
int context = rkt_webview_new_context("", nullptr);
|
||||
rkt_webview_set_loglevel(rkt_webview_loglevel_t::log_debug);
|
||||
|
||||
rkt_webview_register_evt_callback(evt_cb);
|
||||
|
||||
int wv = rkt_webview_create(context, 0);
|
||||
rkt_webview_set_title(wv, "Hi there, this is a title!");
|
||||
rkt_webview_set_icon(wv, "../../rktplayer.png");
|
||||
|
||||
rkt_data_t *d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
|
||||
rkt_webview_move(wv, 100, 200);
|
||||
rkt_webview_resize(wv, 800, 600);
|
||||
rkt_webview_set_url(wv, "https://wikipedia.org");
|
||||
|
||||
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);
|
||||
ProtocolClient client(program);
|
||||
if (!client.start()) {
|
||||
client.abort();
|
||||
return 1;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
Sleep(30000);
|
||||
#else
|
||||
sleep(30
|
||||
);
|
||||
#endif
|
||||
d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
|
||||
rkt_webview_close(wv);
|
||||
bool ok = true;
|
||||
ok = expectResult(client.command(CMD_NOOP), 0, "NOOP") && ok;
|
||||
|
||||
d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
const QJsonObject initialInfo = client.command(CMD_INFO);
|
||||
ok = expectResult(initialInfo, 0, "INFO reports no open windows") && ok;
|
||||
|
||||
rkt_webview_cleanup();
|
||||
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 {
|
||||
std::fprintf(stderr, "PASS: context created (%d)\n", context);
|
||||
}
|
||||
|
||||
QJsonObject createData;
|
||||
createData["context"] = context;
|
||||
createData["parent"] = 0;
|
||||
const QJsonObject createResult = client.command(CMD_CREATE_WV, createData);
|
||||
const int webview = createResult["result"].toInt(-1);
|
||||
if (webview <= 0) {
|
||||
std::fprintf(stderr, "FAIL: CREATE_WV returned %d\n", webview);
|
||||
ok = false;
|
||||
} else {
|
||||
std::fprintf(stderr, "PASS: webview created (%d)\n", webview);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
QJsonObject htmlData;
|
||||
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;
|
||||
|
||||
QJsonObject validData;
|
||||
validData["wv"] = webview;
|
||||
ok = expectResult(client.command(CMD_HANDLE_IS_VALID, validData), 1,
|
||||
"HANDLE_IS_VALID before close") && ok;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,38 @@
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
|
||||
typedef struct {
|
||||
std::chrono::time_point<std::chrono::system_clock> start;
|
||||
} timer;
|
||||
|
||||
|
||||
|
||||
static int _log_level = LOG_INFO;
|
||||
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" {
|
||||
int logLevel()
|
||||
{
|
||||
return _log_level;
|
||||
@@ -26,26 +46,48 @@ void setLogLevel(int l)
|
||||
const char *logIndicator(int l)
|
||||
{
|
||||
switch(l) {
|
||||
case LOG_ERROR: return "ERROR ";
|
||||
case LOG_INFO: return "INFO ";
|
||||
case LOG_DEBUG: return "DEBUG ";
|
||||
case LOG_WARNING: return "WARNING";
|
||||
case LOG_ERROR: return "error";
|
||||
case LOG_INFO: return "info";
|
||||
case LOG_DEBUG: return "debug";
|
||||
case LOG_WARNING: return "warning";
|
||||
}
|
||||
return "UNKNOWN";
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void logElapsed()
|
||||
double logElapsedSeconds()
|
||||
{
|
||||
if (_timer == nullptr) {
|
||||
_timer = new timer;
|
||||
_timer->start = std::chrono::system_clock::now();
|
||||
}
|
||||
|
||||
auto c = std::chrono::system_clock::now();
|
||||
auto duration = c - _timer->start;
|
||||
auto milliseconds
|
||||
const auto current = std::chrono::system_clock::now();
|
||||
const auto milliseconds
|
||||
= std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
duration)
|
||||
current - _timer->start)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
//#define DEBUG_RKT_WEBVIEW
|
||||
|
||||
#include <string>
|
||||
#include "rktwebview_global.h"
|
||||
#include "rktwebview_types.h"
|
||||
|
||||
inline std::string basedir(const std::string &path)
|
||||
@@ -27,35 +30,40 @@ inline std::string basedir(const std::string &path)
|
||||
return r;
|
||||
}
|
||||
|
||||
int logLevel();
|
||||
void setLogLevel(int l);
|
||||
void logElapsed();
|
||||
const char *logIndicator(int l);
|
||||
extern "C" {
|
||||
#ifdef RKTWEBVIEW_PRG_EXE
|
||||
#undef RKTWEBVIEW_EXPORT
|
||||
#define RKTWEBVIEW_EXPORT
|
||||
#endif
|
||||
|
||||
RKTWEBVIEW_EXPORT int logLevel();
|
||||
RKTWEBVIEW_EXPORT void setLogLevel(int l);
|
||||
RKTWEBVIEW_EXPORT double logElapsedSeconds();
|
||||
RKTWEBVIEW_EXPORT const char *logIndicator(int l);
|
||||
RKTWEBVIEW_EXPORT void logMessage(int level, const char *format, ...);
|
||||
}
|
||||
|
||||
#define LOG_ERROR 1
|
||||
#define LOG_WARNING 2
|
||||
#define LOG_INFO 3
|
||||
#define LOG_DEBUG 4
|
||||
|
||||
#define MKLOGSTMT(level, code) if (logLevel() >= level) { fprintf(stderr, "%s: ", logIndicator(level));logElapsed();code;fflush(stderr); }
|
||||
#define MKL0(level, msg) MKLOGSTMT(level, fprintf(stderr, msg))
|
||||
#define MKL1(level, msg, a) MKLOGSTMT(level, fprintf(stderr, msg, a))
|
||||
#define MKL2(level, msg, a, b) MKLOGSTMT(level, fprintf(stderr, msg, a, b))
|
||||
#define ERROR0(msg) logMessage(LOG_ERROR, msg)
|
||||
#define WARN0(msg) logMessage(LOG_WARNING, msg)
|
||||
#define INFO0(msg) logMessage(LOG_INFO, msg)
|
||||
#define DEBUG0(msg) logMessage(LOG_DEBUG, msg)
|
||||
|
||||
#define ERROR0(msg) MKL0(LOG_ERROR, msg)
|
||||
#define WARN0(msg) MKL0(LOG_WARNING, msg)
|
||||
#define INFO0(msg) MKL0(LOG_INFO, msg)
|
||||
#define DEBUG0(msg) MKL0(LOG_DEBUG, msg)
|
||||
#define ERROR1(msg, a) logMessage(LOG_ERROR, msg, a)
|
||||
#define WARN1(msg, a) logMessage(LOG_WARNING, msg, a)
|
||||
#define INFO1(msg, a) logMessage(LOG_INFO, msg, a)
|
||||
#define DEBUG1(msg, a) logMessage(LOG_DEBUG, msg, a)
|
||||
|
||||
#define ERROR1(msg, a) MKL1(LOG_ERROR, msg, a)
|
||||
#define WARN1(msg, a) MKL1(LOG_WARNING, msg, a)
|
||||
#define INFO1(msg, a) MKL1(LOG_INFO, msg, a)
|
||||
#define DEBUG1(msg, a) MKL1(LOG_DEBUG, msg, a)
|
||||
#define ERROR2(msg, a, b) logMessage(LOG_ERROR, msg, a, b)
|
||||
#define WARN2(msg, a, b) logMessage(LOG_WARNING, msg, a, b)
|
||||
#define INFO2(msg, a, b) logMessage(LOG_INFO, msg, a, b)
|
||||
#define DEBUG2(msg, a, b) logMessage(LOG_DEBUG, msg, a, b)
|
||||
|
||||
#define ERROR2(msg, a, b) MKL2(LOG_ERROR, msg, a, b)
|
||||
#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) logMessage(LOG_INFO, msg, a, b, c)
|
||||
|
||||
inline void do_free_data(rkt_data_t *d)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user