462 lines
15 KiB
C++
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");
|
|
}
|