Omzetting naar stdio.
This commit is contained in:
+281
-74
@@ -1,94 +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.11.1\\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
|
||||
|
||||
rkt_webview_init(__FUNCTION__);
|
||||
|
||||
int context = rkt_webview_new_context("", nullptr);
|
||||
rkt_webview_set_loglevel(rkt_webview_loglevel_t::log_debug);
|
||||
|
||||
const char *icon_file = "../../resource/rktplayer.png";
|
||||
FILE *f = fopen(icon_file, "rb");
|
||||
if (f == nullptr) {
|
||||
WARN1("Cannot find icon file %s\n", icon_file);
|
||||
} else {
|
||||
fclose(f);
|
||||
ProtocolClient client(program);
|
||||
if (!client.start()) {
|
||||
client.abort();
|
||||
return 1;
|
||||
}
|
||||
int tray_wv = rkt_webview_tray_create(icon_file, "This is a test tray icon");
|
||||
INFO1("tray_wv = %d\n", tray_wv);
|
||||
|
||||
rkt_webview_register_evt_callback(evt_cb);
|
||||
bool ok = true;
|
||||
ok = expectResult(client.command(CMD_NOOP), 0, "NOOP") && ok;
|
||||
|
||||
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");
|
||||
const QJsonObject initialInfo = client.command(CMD_INFO);
|
||||
ok = expectResult(initialInfo, 0, "INFO reports no open windows") && ok;
|
||||
|
||||
rkt_data_t *d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
if (!protocolOnly && ok) {
|
||||
QJsonObject contextData;
|
||||
contextData["boilerplate_js"] = "";
|
||||
contextData["has_pem_cert"] = false;
|
||||
contextData["pem_cert"] = "";
|
||||
|
||||
rkt_webview_move(wv, 100, 200);
|
||||
rkt_webview_resize(wv, 800, 600);
|
||||
//rkt_webview_set_url(wv, "https://wikipedia.org");
|
||||
rkt_webview_set_html(wv, "<html><head><title>Hi!</title></head><body><h1>Oke test</h1><select id=\"sel-lang\"><option value=\"nl\" selected>Nederlands</option><option value=\"en\">English</option></select><p>Ja</p></body></html>");
|
||||
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);
|
||||
}
|
||||
|
||||
d = rkt_webview_info();
|
||||
fprintf(stderr, "%s\n", d->data.metrics.log_file);
|
||||
rkt_webview_free_data(d);
|
||||
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);
|
||||
}
|
||||
|
||||
while(rkt_webview_events_waiting() > 0) {
|
||||
rkt_data_t *d = rkt_webview_get_event();
|
||||
rkt_webview_free_data(d);
|
||||
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;
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
Sleep(15000);
|
||||
rkt_webview_tray_show_message(tray_wv, "This is a title", "This is a message to display<br><b>Hopefully</b> it does display.");
|
||||
Sleep(15000);
|
||||
#else
|
||||
sleep(30
|
||||
);
|
||||
#endif
|
||||
d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
|
||||
rkt_webview_close(wv);
|
||||
|
||||
d = rkt_webview_info();
|
||||
rkt_webview_free_data(d);
|
||||
|
||||
rkt_webview_close(tray_wv);
|
||||
|
||||
rkt_webview_cleanup();
|
||||
const bool stopped = client.stop();
|
||||
return ok && stopped ? 0 : 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user