Files
racket-webview-qt/main.cpp
T
2026-08-03 08:22:42 +02:00

462 lines
15 KiB
C++

#include <QApplication>
#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"
namespace {
constexpr int STDIO_PROTOCOL_VERSION = 1;
std::mutex output_mutex;
void writeMessage(const QJsonObject &message)
{
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:
Rktwebview_qt *webviewHandler = nullptr;
protected:
void run() override;
};
void eventCallback(rkt_data_t *data)
{
if (data == nullptr || data->kind != rkt_data_kind_t::event) {
freeData(data);
return;
}
QJsonObject message;
message["type"] = "event";
message["wv"] = data->data.event.wv;
message["data"] = QString::fromUtf8(data->data.event.event);
writeMessage(message);
freeData(data);
}
} // namespace
int main(int argc, char *argv[])
{
INFO0("Starting stdio backend\n");
Handler commandHandler;
commandHandler.webviewHandler = new Rktwebview_qt(argc, argv);
commandHandler.webviewHandler->initApp();
commandHandler.start();
sendReady();
commandHandler.webviewHandler->execApp();
INFO0("Qt event loop stopped; waiting for stdin handler\n");
commandHandler.wait();
delete commandHandler.webviewHandler;
commandHandler.webviewHandler = nullptr;
INFO0("Exiting stdio backend\n");
return 0;
}
void Handler::run()
{
std::string line;
bool quit = false;
while (!quit && std::getline(std::cin, line)) {
if (line.empty()) {
continue;
}
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;
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 {
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;
}
}
}
if (!quit) {
INFO0("stdin closed; stopping Qt backend\n");
webviewHandler->closeAllWindows();
webviewHandler->rktQuit();
}
DEBUG0("Exiting stdin handler\n");
}