#include #include #include #include #include #include #include #include #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 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(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"); }