#include "rktwebview_qt.h" #include "webviewqt.h" #include "rktwebview.h" #include "webviewwindow.h" #include "rktutils.h" #include #include #include #include #include #include #include "command.h" #include static inline char *copyString(const char *s) { int l = strlen(s); char *cpy = static_cast(malloc(l + 1)); memcpy(cpy, s, l + 1); return cpy; } 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(); event_cb_t js_event_cb = reinterpret_cast (f); bool has_scp = cmd->args[2].toBool(); QByteArray scp_pem = cmd->args[3].toByteArray(); WebviewWindow *p; if (_views.contains(parent)) { p = _views[parent]; } else { p = nullptr; } WebviewWindow *w = new WebviewWindow(p, has_scp, scp_pem); 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(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 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, const char *optional_server_cert_pem) { Command c(COMMAND_CREATE); c.args.push_back(parent); void *function = reinterpret_cast(js_evt_cb); QVariant f(QVariant::fromValue(function)); c.args.push_back(f); bool has_scp = (optional_server_cert_pem != nullptr); c.args.push_back(has_scp); QByteArray scp = (optional_server_cert_pem == nullptr) ? QByteArray("") : QByteArray(optional_server_cert_pem); c.args.push_back(scp); 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_data_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_data_t *r = static_cast(malloc(sizeof(rkt_data_t))); r->kind = js_result; r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::eval_js_failed; r->data.js_result.value = copyString(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(r); return ws; } rkt_data_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_data_t *r = static_cast(malloc(sizeof(rkt_js_result_t))); r->kind = js_result; r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed; r->data.js_result.value = copyString(c.result.toString().toUtf8()); return r; } rkt_data_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_data_t *r = static_cast(malloc(sizeof(rkt_data_t))); r->kind = js_result; r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed; r->data.js_result.value = copyString(c.result.toString().toUtf8()); return r; } rkt_data_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_data_t *r = static_cast(malloc(sizeof(rkt_data_t))); r->kind = js_result; r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::choose_dir_failed; r->data.js_result.value = copyString(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 = copyString(msg.toUtf8().constData()); rkt_data_t *d = static_cast(malloc(sizeof(rkt_data_t))); d->kind = rkt_data_kind_t::event; d->data.event.wv = wv; d->data.event.event = evt; js_event_cb(d); } } void Rktwebview_qt::rktQuit() { QList 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(event)); } } void Rktwebview_qt::doEvents() { //QTime ct = QTime::currentTime(); //QTime expire = QTime::currentTime().addMSecs(2); //while(QTime::currentTime() <= expire) { _app->processEvents(); //} // Qt 6.10 --> this leads to a core dump // together with the stopEventloop stuff. // Qt 6.4 seem stable. /*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("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); // See Qt 6.10 remark at doEvents. //connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop); // Because we are using processEvents only (Qt 6.10), we need this dispatcher to // handle deferred Deletes. 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; }