Files
racket-webview/rktwebview_qt/rktwebview_qt.cpp
2026-03-09 09:28:46 +01:00

775 lines
20 KiB
C++

#include "rktwebview_qt.h"
#include "webviewqt.h"
#include "rktwebview.h"
#include "webviewwindow.h"
#include "rktutils.h"
#include <QApplication>
#include <QTimer>
#include <QSemaphore>
#include <QJsonDocument>
#include <QJsonObject>
#include <QAbstractEventDispatcher>
#include "command.h"
#include <QFileDialog>
void Rktwebview_qt::processCommand(Command *cmd)
{
switch(cmd->cmd) {
case COMMAND_QUIT: {
_app->quit();
cmd->done = true;
}
break;
case COMMAND_CREATE: {
int parent = cmd->args[0].toInt();
void *f = cmd->args[1].value<void *>();
event_cb_t js_event_cb = reinterpret_cast <event_cb_t>(f);
WebviewWindow *p;
if (_views.contains(parent)) {
p = _views[parent];
} else {
p = nullptr;
}
WebviewWindow *w = new WebviewWindow(p);
WebViewQt *view = new WebViewQt(nextHandle(), w);
w->addView(view, this);
int id = view->id();
_views[id] = w;
_view_js_callbacks[id] = js_event_cb;
w->show();
while(!w->windowCreated()) {
doEvents();
}
cmd->result = id;
cmd->done = true;
}
break;
case COMMAND_CLOSE: {
doEvents();
int wv = cmd->args[0].toInt();
if (_views.contains(wv)) {
WebviewWindow *w= _views[wv];
_views.remove(wv);
w->closeView();
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SET_OU_TOKEN: {
doEvents();
int wv = cmd->args[0].toInt();
QString ou_token = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w= _views[wv];
w->setOUToken(ou_token);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SET_URL: {
doEvents();
int wv = cmd->args[0].toInt();
QString url = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
WebViewQt *v = w->view();
QUrl u(url);
v->setUrl(u);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SET_HTML: {
doEvents();
int wv = cmd->args[0].toInt();
QString html = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
WebViewQt *v = w->view();
v->setHtml(html);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SET_TITLE: {
doEvents();
int wv = cmd->args[0].toInt();
QString title = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
w->setWindowTitle(title);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_RUN_JS: {
doEvents();
int wv = cmd->args[0].toInt();
QString js = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
w->runJs(js);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_CALL_JS: {
doEvents();
int wv = cmd->args[0].toInt();
QString js = cmd->args[1].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
w->callJs(js, cmd);
} else {
cmd->result = false;
cmd->js_result_ok = false;
cmd->done = true;
}
}
break;
case COMMAND_DEV_TOOLS: {
doEvents();
int wv = cmd->args[0].toInt();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
w->openDevTools();
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_SHOW_WIN:
case COMMAND_HIDE_WIN:
case COMMAND_PRESENT_WIN:
case COMMAND_MAX_WIN:
case COMMAND_MIN_WIN:
case COMMAND_SHOW_NORMAL_WIN:
{
doEvents();
int wv = cmd->args[0].toInt();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
int c = cmd->cmd;
if (c == COMMAND_SHOW_WIN) w->show();
else if (c == COMMAND_HIDE_WIN) w->hide();
else if (c == COMMAND_MAX_WIN) w->showMaximized();
else if (c == COMMAND_MIN_WIN) {
w->showMinimized();
}
else if (c == COMMAND_SHOW_NORMAL_WIN) w->showNormal();
else if (c == COMMAND_PRESENT_WIN) {
w->show();
w->raise();
w->activateWindow();
}
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_WINDOW_STATUS:
{
doEvents();
int wv = cmd->args[0].toInt();
window_state_t ws = window_state_t::invalid;
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
if (w->isHidden()) {
ws = window_state_t::hidden;
} else if (w->isMinimized()) {
ws = window_state_t::minimized;
} else if (w->isMaximized()) {
if (w->isActiveWindow()) {
ws = window_state_t::maximized_active;
} else {
ws = window_state_t::maximized;
}
} else if (w->isVisible()) {
if (w->isActiveWindow()) {
ws = window_state_t::normal_active;
} else {
ws = window_state_t::normal;
}
}
}
cmd->result = static_cast<int>(ws);
cmd->done = true;
}
break;
case COMMAND_MOVE: {
doEvents();
int wv = cmd->args[0].toInt();
int x = cmd->args[1].toInt();
int y = cmd->args[2].toInt();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
int move_count = w->moveCount();
w->move(x, y);
while (w->moveCount() == move_count) {
doEvents();
}
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_RESIZE: {
doEvents();
int wv = cmd->args[0].toInt();
int width = cmd->args[1].toInt();
int height = cmd->args[2].toInt();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
int resize_count = w->resizeCount();
w->resize(width, height);
while (w->resizeCount() == resize_count) {
doEvents();
}
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_CHOOSE_DIR: {
int wv = cmd->args[0].toInt();
QString title = cmd->args[1].toString();
QString base_dir = cmd->args[2].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
QString dir = QFileDialog::getExistingDirectory(w, title, base_dir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (dir == "") {
QJsonObject obj;
obj["state"] = "canceled";
obj["dir"] = base_dir;
cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact));
} else {
QJsonObject obj;
obj["state"] = "choosen";
obj["dir"] = dir;
cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact));
}
} else {
cmd->js_result_ok = false;
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_FILE_OPEN:
case COMMAND_FILE_SAVE: {
int wv = cmd->args[0].toInt();
QString title = cmd->args[1].toString();
QString base_dir = cmd->args[2].toString();
QString exts = cmd->args[3].toString();
if (_views.contains(wv)) {
WebviewWindow *w = _views[wv];
QString file;
QString selected_filter;
if (cmd->cmd == COMMAND_FILE_OPEN) {
file = QFileDialog::getOpenFileName(w, title, base_dir, exts, &selected_filter);
} else {
file = QFileDialog::getSaveFileName(w, title, base_dir, exts, &selected_filter);
}
if (file == "") {
QJsonObject obj;
obj["state"] = "canceled";
obj["file"] = "";
obj["used-filter"] = selected_filter;
cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact));
} else {
QJsonObject obj;
obj["state"] = "choosen";
obj["file"] = file;
obj["used-filter"] = selected_filter;
cmd->result = QString::fromUtf8(QJsonDocument(obj).toJson(QJsonDocument::JsonFormat::Compact));
}
} else {
cmd->js_result_ok = false;
cmd->result = false;
}
cmd->done = true;
}
break;
default: {
cmd->result = false;
cmd->done = true;
}
break;
}
}
void Rktwebview_qt::processJsEventQueues()
{
QList<rktwebview_t> wvs = _views.keys();
int i, N;
for(i = 0, N = wvs.length(); i < N; i++) {
int wv = wvs[i];
if (_views.contains(wv)) {
WebviewWindow *win = _views[wv];
win->processJsEvents();
}
}
}
void Rktwebview_qt::removeView(int id)
{
if (_views.contains(id)) {
WebviewWindow *win = _views[id];
_views.remove(id);
_view_js_callbacks.remove(id);
}
}
int Rktwebview_qt::nextHandle()
{
int h = ++_current_handle;
return h;
}
int Rktwebview_qt::rktWebViewCreate(int parent, event_cb_t js_evt_cb)
{
Command c(COMMAND_CREATE);
c.args.push_back(parent);
void *function = reinterpret_cast<void *>(js_evt_cb);
QVariant f(QVariant::fromValue(function));
c.args.push_back(f);
postCommand(&c);
while(!c.done) { doEvents(); }
int id = c.result.toInt();
return id;
}
void Rktwebview_qt::rktWebViewClose(int wv)
{
Command c(COMMAND_CLOSE);
c.args.push_back(wv);
postCommand(&c);
while(!c.done) { doEvents(); }
}
void Rktwebview_qt::rktSetOUToken(rktwebview_t wv, const char *ou_token)
{
Command c(COMMAND_SET_OU_TOKEN);
c.args.push_back(wv);
QString ou_tok(ou_token);
c.args.push_back(ou_tok);
postCommand(&c);
while(!c.done) { doEvents(); }
}
result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url)
{
Command c(COMMAND_SET_URL);
QString _url(url);
c.args.push_back(wv);
c.args.push_back(_url);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::set_navigate_failed;
}
result_t Rktwebview_qt::rktSetHtml(rktwebview_t wv, const char *html)
{
Command c(COMMAND_SET_HTML);
QString _html(html);
c.args.push_back(wv);
c.args.push_back(_html);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::set_html_failed;
}
rkt_js_result_t *Rktwebview_qt::rktCallJs(rktwebview_t wv, const char *js)
{
Command c(COMMAND_CALL_JS);
QString _js(js);
c.args.push_back(wv);
c.args.push_back(_js);
postCommand(&c);
while(!c.done) { doEvents(); }
rkt_js_result_t *r = static_cast<rkt_js_result_t *>(malloc(sizeof(rkt_js_result_t)));
r->result = c.js_result_ok ? result_t::oke : result_t::eval_js_failed;
r->value = strdup(c.result.toString().toUtf8());
return r;
}
result_t Rktwebview_qt::rktRunJs(rktwebview_t wv, const char *js)
{
Command c(COMMAND_RUN_JS);
QString _js(js);
c.args.push_back(wv);
c.args.push_back(_js);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::eval_js_failed;
}
result_t Rktwebview_qt::rktMove(rktwebview_t wv, int x, int y)
{
Command c(COMMAND_MOVE);
c.args.push_back(wv);
c.args.push_back(x);
c.args.push_back(y);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::move_failed;
}
result_t Rktwebview_qt::rktResize(rktwebview_t wv, int w, int h)
{
Command c(COMMAND_RESIZE);
c.args.push_back(wv);
c.args.push_back(w);
c.args.push_back(h);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::resize_failed;
}
result_t Rktwebview_qt::rktHideWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_HIDE_WIN, result_t::failed);
}
result_t Rktwebview_qt::rktShowWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_SHOW_WIN, result_t::failed);
}
result_t Rktwebview_qt::rktPresentWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_PRESENT_WIN, result_t::failed);
}
result_t Rktwebview_qt::rktMaximizeWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_MAX_WIN, result_t::failed);
}
result_t Rktwebview_qt::rktMinimizeWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_MIN_WIN, result_t::failed);
}
result_t Rktwebview_qt::rktShowNormalWindow(rktwebview_t w)
{
return doWindow(w, COMMAND_SHOW_NORMAL_WIN, result_t::failed);
}
window_state_t Rktwebview_qt::rktWindowState(rktwebview_t w)
{
Command c(COMMAND_WINDOW_STATUS);
c.args.push_back(w);
postCommand(&c);
while(!c.done) { doEvents(); }
int r = c.result.toInt();
window_state_t ws = static_cast<window_state_t>(r);
return ws;
}
rkt_js_result_t *Rktwebview_qt::rktChooseDir(rktwebview_t w, const char *title, const char *base_dir)
{
Command c(COMMAND_CHOOSE_DIR);
c.args.push_back(w);
QString t(title);
c.args.push_back(t);
QString dir(base_dir);
c.args.push_back(dir);
postCommand(&c);
while(!c.done) { doEvents(); }
bool oke = c.js_result_ok;
rkt_js_result_t *r = static_cast<rkt_js_result_t *>(malloc(sizeof(rkt_js_result_t)));
r->result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed;
r->value = strdup(c.result.toString().toUtf8());
return r;
}
rkt_js_result_t *Rktwebview_qt::rktFileOpen(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
Command c(COMMAND_FILE_OPEN);
c.args.push_back(w);
QString t(title);
c.args.push_back(t);
QString dir(base_dir);
c.args.push_back(dir);
QString exts(permitted_exts);
c.args.push_back(exts);
postCommand(&c);
while(!c.done) { doEvents(); }
bool oke = c.js_result_ok;
rkt_js_result_t *r = static_cast<rkt_js_result_t *>(malloc(sizeof(rkt_js_result_t)));
r->result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed;
r->value = strdup(c.result.toString().toUtf8());
return r;
}
rkt_js_result_t *Rktwebview_qt::rktFileSave(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
Command c(COMMAND_FILE_SAVE);
c.args.push_back(w);
QString t(title);
c.args.push_back(t);
QString dir(base_dir);
c.args.push_back(dir);
QString exts(permitted_exts);
c.args.push_back(exts);
postCommand(&c);
while(!c.done) { doEvents(); }
bool oke = c.js_result_ok;
rkt_js_result_t *r = static_cast<rkt_js_result_t *>(malloc(sizeof(rkt_js_result_t)));
r->result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed;
r->value = strdup(c.result.toString().toUtf8());
return r;
}
result_t Rktwebview_qt::rktWindowSetTitle(rktwebview_t wv, const char *title)
{
Command c(COMMAND_SET_TITLE);
c.args.push_back(wv);
c.args.push_back(title);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::failed;
}
result_t Rktwebview_qt::doWindow(rktwebview_t w, int cmd, result_t on_error)
{
Command c(cmd);
c.args.push_back(w);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : on_error;
}
bool Rktwebview_qt::rktValid(rktwebview_t wv)
{
return _views.contains(wv);
}
result_t Rktwebview_qt::rktOpenDevtools(rktwebview_t wv)
{
Command c(COMMAND_DEV_TOOLS);
c.args.push_back(wv);
postCommand(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::eval_js_failed;
}
void Rktwebview_qt::onPageLoad(rktwebview_t w)
{
}
void Rktwebview_qt::pageLoaded(rktwebview_t w, bool ok)
{
runJs(w,
"if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n"
"window.rkt_send_event = function(obj) {\n"
" console.log('Sending event: ' + obj);\n"
" window.rkt_event_queue.push(obj);\n"
"};\n"
"window.rkt_get_events = function() {\n"
" let q = window.rkt_event_queue;\n"
" window.rkt_event_queue = [];\n"
" let json_q = JSON.stringify(q);\n"
" return json_q;\n"
"};\n"
);
// trigger page loaded.
EventContainer e("page-loaded");
e["oke"] = ok;
triggerEvent(w, e);
}
void Rktwebview_qt::triggerEvent(rktwebview_t wv, const EventContainer &e)
{
triggerEvent(wv, mkEventJson(e));
}
void Rktwebview_qt::triggerEvent(rktwebview_t wv, const QString &msg)
{
if (_view_js_callbacks.contains(wv)) {
event_cb_t js_event_cb = _view_js_callbacks[wv];
char *evt = strdup(msg.toUtf8().constData());
rkt_event_t *e = static_cast<rkt_event_t *>(malloc(sizeof(rkt_event_t)));
e->wv = wv;
e->event = evt;
js_event_cb(e);
}
}
void Rktwebview_qt::rktQuit()
{
QList<int> keys = _views.keys();
int i;
for(i = 0; i < keys.size(); i++) {
int view_handle = keys[i];
rktWebViewClose(view_handle);
}
Command c(COMMAND_QUIT);
postCommand(&c);
while(!c.done) { doEvents(); }
}
void Rktwebview_qt::runJs(rktwebview_t wv, const char *js)
{
if (_views.contains(wv)) {
QString _js(js);
WebviewWindow *win = _views[wv];
win->runJs(_js);
}
}
void Rktwebview_qt::postCommand(Command *cmd)
{
CommandEvent *e = new CommandEvent(cmd);
QApplication::postEvent(this, e);
}
void Rktwebview_qt::handleCommandEvent(CommandEvent *e)
{
Command *c = e->cmd();
processCommand(c);
}
void Rktwebview_qt::customEvent(QEvent *event)
{
if (event->type() == COMMAND_EVENT) {
handleCommandEvent(static_cast<CommandEvent *>(event));
}
}
void Rktwebview_qt::doEvents()
{
//_app->processEvents();
if (_evt_loop_depth == 0) {
_evt_loop_depth += 1;
_evt_loop_timer.setSingleShot(true);
_evt_loop_timer.start(2);
//_evt_loop.exec();
_app->exec();
}
}
void Rktwebview_qt::stopEventloop()
{
//_evt_loop.exit(0);
_app->exit(0);
_evt_loop_depth -= 1;
}
Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) :
QObject()
{
_argc = 1;
_argv[0] = const_cast<char *>("Rktwebview_qt");
_current_handle = 0;
_handler = handler;
_evt_loop_depth = 0;
_app = new QApplication(_argc, _argv);
connect(&_process_events, &QTimer::timeout, this, &Rktwebview_qt::processJsEventQueues);
_process_events.start(5);
connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop);
//const auto *eventDispatcher = QThread::currentThread()->eventDispatcher();
//QObject::connect(eventDispatcher, &QAbstractEventDispatcher::aboutToBlock,
// QThread::currentThread(), []{
// if (QThread::currentThread()->loopLevel() == 0)
// QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
// }
// );
*_handler = nullptr;
}