This commit is contained in:
2026-03-03 16:10:38 +01:00
parent 35aae3b707
commit 5f0b7d3dc8
16 changed files with 766 additions and 334 deletions

View File

@@ -1,20 +1,11 @@
window._web_wire_evt_queue = [];
if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }
window._web_wire_queue_worker = function() {
if (typeof web_ui_wire_handle_event === 'function') {
while (window._web_wire_evt_queue.length > 0) {
let evt = window._web_wire_evt_queue.shift();
web_ui_wire_handle_event(JSON.stringify(evt));
}
}
window.setTimeout(window._web_wire_queue_worker, 5);
window.rkt_put_evt = function(evt) {
window.rkt_event_queue.push(evt);
};
window.setTimeout(window._web_wire_queue_worker, 15);
window._web_wire_put_evt = function(evt) { window._web_wire_evt_queue.push(evt); };
window._web_wire_event_info = function(e, id, evt) {
window.rkt_event_info = function(e, id, evt) {
let obj = {};
if (e == 'input') {
obj['data'] = evt.data;
@@ -53,13 +44,7 @@ window._web_wire_event_info = function(e, id, evt) {
return obj;
};
window._web_wire_get_evts = function() {
let v = _web_wire_evt_queue;
_web_wire_evt_queue = [];
return JSON.stringify(v); // This needs no extra type info, as it is internally used only
};
window._web_wire_bind_evt_ids = function(win_nr, selector, event_kind) {
window.rkt_bind_evt_ids = function(win_nr, selector, event_kind) {
try {
let nodelist = document.querySelectorAll(selector);
if (nodelist === undefined || nodelist === null) {
@@ -75,8 +60,8 @@ window._web_wire_bind_evt_ids = function(win_nr, selector, event_kind) {
el.addEventListener(event_kind,
function(e) {
let obj = {evt: event_kind, id: el_id, selector: selector, window: win_nr,
js_evt: window._web_wire_event_info(event_kind, el_id, e) };
window._web_wire_put_evt(obj);
js_evt: window.rkt_event_info(event_kind, el_id, e) };
window.rkt_put_evt(obj);
}
);
let info = [ el_id, el_tag, el_type ];
@@ -89,35 +74,3 @@ window._web_wire_bind_evt_ids = function(win_nr, selector, event_kind) {
}
};
window._web_wire_resize_timeout = false;
window.addEventListener('resize', function() {
clearTimeout(window._web_wire_resize_timeout);
let f = function() {
let obj = { selector: 'global', evt: 'window-resize', h: window.outerWidth, w: window.outerHeight };
window._web_wire_put_evt(obj);
};
window._web_wire_resize_timeout = setTimeout(f, 250);
});
window._web_wire_x = window.screenX;
window._web_wire_y = window.screenY;
window._web_wire_move_interval = setInterval(function() {
let x = window.screenX;
let y = window.screenY;
if (x != window._web_wire_x || y != window._web_wire_y) {
window._web_wire_x = x;
window._web_wire_y = y;
let obj = { selector: 'global', evt: 'window-move', x: x, y: y };
window._web_wire_put_evt(obj);
}
}, 500);
document.addEventListener('readystatechange', event => {
// When window loaded ( external resources are loaded too- `css`,`src`, etc...)
if (event.target.readyState === "complete") {
let obj = { selector: 'global', evt: 'html-loaded' };
window._web_wire_put_evt(obj);
}
});

View File

@@ -13,6 +13,21 @@
racket/path
)
(provide rkt-wv
rkt-wv-win
rkt-webview-create
rkt-webview-close
rkt-webview-set-url!
rkt-webview-set-html!
rkt-webview-run-js
rkt-webview-move
rkt-webview-resize
rkt-webview-exit
rkt-webview-valid?
rkt-webview-open-devtools
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; FFI Library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -34,10 +49,19 @@
(define webview-lib (ffi-lib webview-lib-file))
(define-ffi-definer define-rktwebview webview-lib)
(define callback-box (box '()))
(define (applier thunk)
(thunk))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Types
;; Types / Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-cstruct _rkt_evt_t
([w _int]
[evt _pointer]
))
;RKTWEBVIEW_QT_EXPORT void rkt_webview_init(int &argc, char **argv);
(define-rktwebview rkt_webview_init
(_fun -> _void))
@@ -48,7 +72,8 @@
;RKTWEBVIEW_QT_EXPORT int rkt_webview_create(int parent);
(define-rktwebview rkt_webview_create
(_fun _int -> _int))
(_fun _int (_fun #:keep callback-box #:async-apply applier
_rkt_evt_t-pointer -> _void) -> _int))
;RKTWEBVIEW_QT_EXPORT void rkt_webview_close(int wv);
(define-rktwebview rkt_webview_close
@@ -59,6 +84,37 @@
(_fun _int _string/utf-8 -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_html(int wv, const char *html);
(define-rktwebview rkt_webview_set_html
(_fun _int _string/utf-8 -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_run_js(int wv, const char *js);
(define-rktwebview rkt_webview_run_js
(_fun _int _string/utf-8 -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_open_devtools(int wv);
(define-rktwebview rkt_webview_open_devtools
(_fun _int -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_destroy_event(rkt_event_t e);
(define-rktwebview rkt_webview_destroy_event
(_fun _rkt_evt_t-pointer -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_move(rktwebview_t w, int x, int y);
(define-rktwebview rkt_webview_move
(_fun _int _int _int -> _int))
;RKTWEBVIEW_QT_EXPORT result_t rkt_webview_resize(rktwebview_t w, int width, int height);
(define-rktwebview rkt_webview_resize
(_fun _int _int _int -> _int))
;RKTWEBVIEW_QT_EXPORT bool rkt_webview_valid(rktwebview_t wv);
(define-rktwebview rkt_webview_valid
(_fun _int -> _int))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Initialize and start library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define process-events #t)
@@ -80,3 +136,94 @@
(rkt_webview_init)
(start-event-processing)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Provided features
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-struct rkt-wv
(win evt-queue callback [valid #:mutable])
#:transparent
)
(define rkt-wv-store (make-hash))
(define (rkt-process-events handle)
(if (> (queue-length (rkt-wv-evt-queue handle)) 0)
(let ((e (dequeue! (rkt-wv-evt-queue handle))))
(if (symbol? e)
(if (eq? e 'quit)
(begin
(hash-remove! rkt-wv-store (rkt-wv-win handle))
'quit)
(rkt-process-events handle))
(let ((evt (cast (rkt_evt_t-evt e) _pointer _string*/utf-8)))
((rkt-wv-callback handle) handle evt)
(rkt_webview_destroy_event e)
(rkt-process-events handle)))
)
'done))
(define (rkt-webview-create parent evt-callback)
(let ((evt-queue (make-queue)))
(let ((wv (rkt_webview_create parent
(λ (rkt-evt)
(enqueue! evt-queue rkt-evt)))))
(let ((handle (make-rkt-wv wv evt-queue evt-callback #t)))
(thread (λ ()
(sleep 1)
(letrec ((f (λ ()
(let ((r (rkt-process-events handle)))
(if (eq? r 'quit)
(begin
(set-rkt-wv-valid! handle #f)
(displayln "Quitting event loop")
'done)
(begin
;(displayln "Waiting for events.")
(sleep 0.01)
(f)))))))
(f))))
(hash-set! rkt-wv-store (rkt-wv-win handle) handle)
handle))))
(define (rkt-webview-close handle)
(rkt_webview_close (rkt-wv-win handle))
(enqueue! (rkt-wv-evt-queue handle) 'quit)
#t)
(define (rkt-webview-set-url! wv url)
(rkt_webview_set_url (rkt-wv-win wv) url))
(define (rkt-webview-set-html! wv html)
(rkt_webview_set_html (rkt-wv-win wv) html))
(define (rkt-webview-run-js wv js)
(rkt_webview_run_js (rkt-wv-win wv) js))
(define (rkt-webview-resize wv w h)
(rkt_webview_resize (rkt-wv-win wv) w h))
(define (rkt-webview-move wv x y)
(rkt_webview_move (rkt-wv-win wv) x y))
(define (rkt-webview-open-devtools wv)
(rkt_webview_open_devtools (rkt-wv-win wv)))
(define (rkt-webview-valid? wv)
(if (eq? (rkt-wv-valid wv) #f)
#f
(if (= (rkt_webview_valid wv) 0)
#f
#t)))
(define (rkt-webview-exit)
(let ((open-windows (hash->list rkt-wv-store)))
(for-each (λ (kv)
(let ((win (car kv))
(handle (cdr kv)))
(rkt-webview-close handle)))
open-windows))
(stop-event-processing))

View File

@@ -1,6 +1,6 @@
#lang racket/base
(require "racket-webview-ffi.rkt"
(require "racket-webview-qt.rkt"
"utils.rkt"
web-server/servlet
web-server/servlet-env
@@ -16,16 +16,17 @@
(provide webview-create
webview-devtools
webview-has-events?
webview-event-count
webview-get-event
webview-set-event-callback!
webview-clear-event-callback!
webview-bind
webview-close
webview-run-js
;webview-call-js
webview-move
webview-resize
webview-bind!
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Web server
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define current-servlet-port 8083)
(define current-window-nr 1)
@@ -36,7 +37,9 @@
(file->string file)))
(define-struct wv
(handle port window-nr
([handle #:mutable]
port
[window-nr #:mutable]
[file-getter #:mutable]
[boilerplate-js #:mutable]
[webserver-thread #:mutable]))
@@ -91,78 +94,64 @@
;#:launch-browser #f
#:servlet-regexp #rx""))))
(define (webview-create file-getter #:boilerplate-js [bj (default-boilerplate-js)])
(let* ((wv (rkt_create_webview))
(h (make-wv wv current-servlet-port current-window-nr file-getter bj #f))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utilities
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (util-parse-event evt)
(let ((wv-d0 (with-input-from-string evt read-json)))
wv-d0))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Webview functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define (webview-create file-getter event-callback
#:boilerplate-js [bj (default-boilerplate-js)]
#:parent [p 0])
(let* ((h (make-wv #f current-servlet-port -1 file-getter bj #f))
(server (let ((s (start-web-server h)))
(sleep 1)
s))
(event-processor (λ (wv evt)
(event-callback h (util-parse-event evt))))
(wv (rkt-webview-create p event-processor))
(base-req (format "http://127.0.0.1:~a"
(wv-port h)))
)
(set-wv-webserver-thread! h (start-web-server h))
(rkt_webview_navigate (wv-handle h) base-req)
(set-wv-handle! h wv)
(set-wv-window-nr! h (rkt-wv-win wv))
(set-wv-webserver-thread! h server)
(rkt-webview-set-url! (wv-handle h) base-req)
(set! current-servlet-port (+ current-servlet-port 1))
(set! current-window-nr (+ current-window-nr 1))
h))
(define (webview-devtools wv)
(rkt_webview_devtools (wv-handle wv)))
(rkt-webview-open-devtools (wv-handle wv)))
(define (webview-parse-event evt)
(let ((wv-d0 (with-input-from-string evt read-json)))
;(displayln wv-d0)
(let ((wv-d1 (with-input-from-string (hash-ref wv-d0 'data) read-json)))
;(displayln wv-d1)
(let ((wv-d2 (with-input-from-string (car wv-d1) read-json)))
;(displayln wv-d2)
wv-d2))))
(define (webview-move wv x y)
(rkt-webview-move (wv-handle wv) x y))
(define (webview-set-event-callback! wv cb)
(rkt_webview_set_event_callback! (wv-handle wv) (wv-window-nr wv)
(λ (context data)
(let ((e (webview-parse-event data)))
(cb context e)))))
(define (webview-resize wv w h)
(rkt-webview-resize (wv-handle wv) w h))
(define (webview-clear-event-callback! wv)
(rkt_webview_clear_event_callback! (wv-handle wv) (wv-window-nr wv)))
(define (webview-get-event wv)
(if (> (rkt_webview_pending_events (wv-handle wv)) 0)
(let ((item (rkt_webview_get_event (wv-handle wv))))
(let ((data (rkt_webview_item_data item)))
(rkt_webview_destroy_item item)
(let ((wv-d0 (with-input-from-string data read-json)))
;(displayln wv-d0)
(let ((wv-d1 (with-input-from-string (hash-ref wv-d0 'data) read-json)))
;(displayln wv-d1)
(let ((wv-d2 (with-input-from-string (car wv-d1) read-json)))
;(displayln wv-d2)
wv-d2
)
)
)
)
)
#f)
(define (webview-close wv)
(rkt-webview-close (wv-handle wv))
(kill-thread (wv-webserver-thread wv))
)
(define (webview-has-events? wv)
(>= (rkt_webview_pending_events (wv-handle wv)) 1)
)
(define (webview-event-count wv)
(rkt_webview_pending_events (wv-handle wv)))
(define (webview-bind wv selector event)
(define (webview-bind! wv selector event)
(let ((sel (if (symbol? selector)
(format "#~a" selector)
selector))
(evt (format "~a" event)))
(rkt_webview_run_js (wv-handle wv)
(format "window._web_wire_bind_evt_ids(~a, '~a', '~a')"
(webview-run-js wv
(format "window.rkt_bind_evt_ids(~a, '~a', '~a')"
(wv-window-nr wv) sel evt))))
(define (webview-run-js wv js)
(rkt_webview_run_js (wv-handle wv) js))
(rkt-webview-run-js (wv-handle wv) js))
;(define (webview-call-js wv js)
; (let ((result (rkt_webview_call_js (wv-handle wv) js)))
@@ -184,13 +173,16 @@
p)))
(define (test)
(let ((h (webview-create file-getter)))
(displayln (webview-has-events? h))
(while (not (webview-has-events? h))
(displayln "Waiting...")
(sleep 1))
(let ((evt (webview-get-event h)))
(when (string=? (hash-ref evt 'evt) "html-loaded")
(webview-bind h "button" "click")))
(let* ((cb (λ (handle evt)
(displayln evt)))
(h (webview-create file-getter cb)))
h))
; (while (not (webview-has-events? h))
; (displayln "Waiting...")
; (sleep 1))
; (let ((evt (webview-get-event h)))
; (when (string=? (hash-ref evt 'evt) "html-loaded")
; (webview-bind h "button" "click")))
; h))

View File

@@ -22,6 +22,7 @@ add_library(rktwebview_qt SHARED
rktwebview_internal.h
webviewwindow.h webviewwindow.cpp
webviewapp.h webviewapp.cpp
rktutils.h rktutils.cpp
)
target_link_libraries(rktwebview_qt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
@@ -40,6 +41,7 @@ add_executable(rktwebview_qt_test
webviewwindow.h webviewwindow.cpp
rktwebview_internal.h
webviewapp.h webviewapp.cpp
rktutils.h rktutils.cpp
)
target_compile_definitions(rktwebview_qt_test PRIVATE RKTWEBVIEW_QT_LIBRARY)

View File

@@ -5,6 +5,12 @@
static int _argc;
static char **_argv;
void eventCb(rkt_event_t *e)
{
printf("event: %s\n", e->event);
rkt_webview_destroy_event(e);
}
int main(int argc, char *argv[])
{
int wv1;
@@ -14,20 +20,30 @@ int main(int argc, char *argv[])
_argv = argv;
rkt_webview_init();
wv1 = rkt_webview_create(0);
wv1 = rkt_webview_create(0, eventCb);
rkt_webview_set_url(wv1, "https://wikipedia.org");
int i = 0;
while(i < 20) {
while(i < 60) {
printf("Waiting...%d\n", i);
rkt_webview_process_events(1000);
if (i == 10) {
wv2 = rkt_webview_create(0);
if (i == 6) {
rkt_webview_open_devtools(wv1);
}
if (i == 15) {
rkt_webview_close(wv1);
if (i == 10) {
wv2 = rkt_webview_create(0, eventCb);
}
if (i > 10) {
char buf[1000];
sprintf(buf, "{ let obj = { e: 'test', i: %d }; window.rkt_send_event(obj); }", i);
rkt_webview_run_js(wv1, buf);
}
if (i == 24) {
rkt_webview_close(wv2);
}
i += 1;
}

View File

@@ -0,0 +1,27 @@
#include "rktutils.h"
#include <QJsonDocument>
#include <QJsonObject>
#include <QMetaType>
QString mkEventJson(const EventContainer &kv)
{
QJsonObject obj;
QList<QString> keys = kv.keys();
int i, N;
for(i = 0, N = keys.length(); i < N; i++) {
const QString &key = keys[i];
const QVariant &v = kv[key];
obj[key] = v.toJsonValue();
}
QJsonDocument doc(obj);
return QString::fromUtf8(doc.toJson(QJsonDocument::JsonFormat::Compact));
}
QHash<QString, QVariant> mkEvent()
{
QHash<QString, QVariant> h;
return h;
}

18
rktwebview_qt/rktutils.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef __RKT_UTILS_H__
#define __RKT_UTILS_H__
#include <QHash>
#include <QString>
#include <QVariant>
class EventContainer : public QHash<QString, QVariant>
{
public:
EventContainer(const QString &evt) {
this->insert("event", evt);
}
};
QString mkEventJson(const EventContainer &kv);
#endif

View File

@@ -27,127 +27,56 @@ Rktwebview_qt *handler = nullptr;
void rkt_webview_init()
{
/*
if (!started) {
if (pipe(pipefd) != -1) {
webview_process = fork();
if (webview_process < 0)
cannot_fork_or_pipe = true;
} else if (webview_process == 0) {
WebViewApp app;
started = true;
} else {
cannot_fork_or_pipe = true;
}
}
*/
if (handler == nullptr) {
handler = new Rktwebview_qt(&handler);
}
}
int rkt_webview_create(int parent, void (*js_event_cb)(const char *))
int rkt_webview_create(rktwebview_t parent, event_cb_t js_event_cb)
{
rkt_webview_init();
return handler->rktWebViewCreate(parent, js_event_cb);
}
void rkt_webview_close(int wv)
void rkt_webview_close(rktwebview_t wv)
{
rkt_webview_init();
handler->rktWebViewClose(wv);
}
result_t rkt_webview_set_url(int wv, const char *url)
result_t rkt_webview_set_url(rktwebview_t wv, const char *url)
{
rkt_webview_init();
result_t r = handler->rktSetUrl(wv, url);
return r;
}
/////////////////////////////////////////////////////////////////////
// Supporting functions
/////////////////////////////////////////////////////////////////////
void queue_init(queue_t **q)
result_t rkt_webview_set_html(rktwebview_t wv, const char *url)
{
*q = static_cast<queue_t *>(malloc(sizeof(queue_t)));
queue_t *Q = *q;
Q->length = 0;
Q->first = nullptr;
Q->last = nullptr;
}
void enqueue(queue_t *q, item_t item)
{
queue_item_t *itm = (queue_item_t *) malloc(sizeof(queue_item_t));
itm->item.context = item.context;
itm->item.data = strdup(item.data);
if (q->first == nullptr) {
q->first = itm;
q->last = itm;
itm->prev = nullptr;
itm->next = nullptr;
q->length = 1;
} else {
itm->prev = q->last;
itm->next = nullptr;
q->last->next = itm;
q->last = itm;
q->length += 1;
}
}
bool dequeue(queue_t *q, item_t *item)
{
if (q->length == 0) {
item->context = CONTEXT_INVALID;
item->data = nullptr;
return false;
} else {
queue_item_t *itm = q->first;
q->first = q->first->next;
q->length -= 1;
if (q->length == 0) {
q->first = nullptr;
q->last = nullptr;
}
item->context = itm->item.context;
item->data = itm->item.data;
free(itm);
return true;
}
}
int queue_length(queue_t *q)
{
return q->length;
}
void queue_destroy(queue_t *q)
{
item_t i;
while(dequeue(q, &i)) {
free(i.data);
}
free(q);
}
void free_item(item_t item)
{
free(item.data);
rkt_webview_init();
result_t r = handler->rktSetHtml(wv, url);
return r;
}
result_t rkt_webview_run_js(rktwebview_t wv, const char *js)
{
rkt_webview_init();
result_t r = handler->rktRunJs(wv, js);
return r;
}
result_t rkt_webview_open_devtools(rktwebview_t wv)
{
rkt_webview_init();
result_t r = handler->rktOpenDevtools(wv);
return r;
}
void rkt_webview_process_events(int for_ms)
{
//rkt_webview_init();
rkt_webview_init();
int64_t start_ms = current_ms();
int64_t end_ms = start_ms + for_ms;
@@ -157,3 +86,35 @@ void rkt_webview_process_events(int for_ms)
handler->doEvents();
}
}
result_t rkt_webview_destroy_event(rkt_event_t *e)
{
free(e->event);
free(e);
return result_t::oke;
}
result_t rkt_webview_move(rktwebview_t wv, int x, int y)
{
rkt_webview_init();
result_t r = handler->rktMove(wv, x, y);
return r;
}
result_t rkt_webview_resize(rktwebview_t wv, int width, int height)
{
rkt_webview_init();
result_t r = handler->rktResize(wv, width, height);
return r;
}
bool rkt_webview_valid(rktwebview_t wv)
{
rkt_webview_init();
return handler->rktValid(wv);
}

View File

@@ -3,31 +3,16 @@
#include "rktwebview_qt_global.h"
#define CONTEXT_QUIT -2
#define CONTEXT_NIL -1
#define CONTEXT_INVALID 0
#define CONTEXT_BOUND_EVENT 1
#define CONTEXT_WINDOW_RESIZE 2
#define CONTEXT_WINDOW_MOVE 3
#define CONTEXT_WINDOW_CAN_CLOSE 4
#define CONTEXT_WINDOW_CLOSED 5
#define CONTEXT_SET_HTML 6
#define CONTEXT_NAVIGATE 7
#define CONTEXT_EVAL_JS 8
#define CONTEXT_OPEN_DEVTOOLS 9
#define CONTEXT_CALL_JS 10
#define CONTEXT_CMD_CLOSE 11
#define CONTEXT_CMD_CREATE_VIEW 12
extern "C" {
typedef int rktwebview_t;
typedef struct {
rktwebview_t wv;
int context;
char *data;
} item_t;
char *event;
} rkt_event_t;
typedef void (*event_cb_t)(rkt_event_t *);
typedef enum {
no_result_yet = -1,
@@ -42,15 +27,29 @@ typedef enum {
webview_invalid_state = 8,
webview_invalid_argument = 9,
webview_unspecified = 10,
webview_dispatch_failed = 11
webview_dispatch_failed = 11,
move_failed = 12,
resize_failed = 13
} result_t;
RKTWEBVIEW_QT_EXPORT void rkt_webview_init();
RKTWEBVIEW_QT_EXPORT void rkt_webview_process_events(int for_ms);
RKTWEBVIEW_QT_EXPORT int rkt_webview_create(int parent, void (*js_event_cb)(const char *msg));
RKTWEBVIEW_QT_EXPORT void rkt_webview_close(int wv);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(int wv, const char *url);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_html(int wv, const char *html);
RKTWEBVIEW_QT_EXPORT int rkt_webview_create(rktwebview_t parent, event_cb_t js_event_cb);
RKTWEBVIEW_QT_EXPORT void rkt_webview_close(rktwebview_t wv);
RKTWEBVIEW_QT_EXPORT bool rkt_webview_valid(rktwebview_t wv);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_url(rktwebview_t wv, const char *url);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_set_html(rktwebview_t wv, const char *html);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_run_js(rktwebview_t wv, const char *js);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_open_devtools(rktwebview_t wv);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_destroy_event(rkt_event_t *e);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_move(rktwebview_t w, int x, int y);
RKTWEBVIEW_QT_EXPORT result_t rkt_webview_resize(rktwebview_t w, int width, int height);
}

View File

@@ -3,23 +3,4 @@
#include "rktwebview.h"
typedef struct _item {
item_t item;
struct _item *next;
struct _item *prev;
} queue_item_t;
typedef struct {
queue_item_t *first;
queue_item_t *last;
int length;
} queue_t;
void queue_init(queue_t **q);
void enqueue(queue_t *q, item_t item);
bool dequeue(queue_t *q, item_t *item);
int queue_length(queue_t *q);
void queue_destroy(queue_t *q);
void free_item(item_t i);
#endif // RKTWEBVIEW_INTERNAL_H

View File

@@ -2,6 +2,7 @@
#include "webviewqt.h"
#include "rktwebview.h"
#include "webviewwindow.h"
#include "rktutils.h"
#include <QApplication>
#include <QTimer>
#include <QSemaphore>
@@ -12,6 +13,11 @@
#define COMMAND_CLOSE 2
#define COMMAND_CREATE 3
#define COMMAND_SET_URL 4
#define COMMAND_SET_HTML 5
#define COMMAND_RUN_JS 6
#define COMMAND_DEV_TOOLS 7
#define COMMAND_MOVE 8
#define COMMAND_RESIZE 9
class Command
{
@@ -27,13 +33,6 @@ public:
}
};
static QString jsonString(QJsonObject obj)
{
QJsonDocument doc(obj);
QString j = QString::fromUtf8(doc.toJson(QJsonDocument::JsonFormat::Compact));
return j;
}
void Rktwebview_qt::processCommands()
{
while(!_command_queue.empty()) {
@@ -49,7 +48,7 @@ void Rktwebview_qt::processCommands()
int parent = cmd->args[0].toInt();
void *f = cmd->args[1].value<void *>();
void (*js_event_cb)(const char *msg) = reinterpret_cast <void(*)(const char *)>(f);
event_cb_t js_event_cb = reinterpret_cast <event_cb_t>(f);
QWidget *p;
if (_views.contains(parent)) {
@@ -78,8 +77,7 @@ void Rktwebview_qt::processCommands()
if (_views.contains(wv)) {
WebviewWindow *w= _views[wv];
_views.remove(wv);
w->close();
w->deleteLater();
w->closeView();
cmd->result = true;
} else {
cmd->result = false;
@@ -102,6 +100,73 @@ void Rktwebview_qt::processCommands()
cmd->done = true;
}
break;
case COMMAND_SET_HTML: {
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_RUN_JS: {
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_DEV_TOOLS: {
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_MOVE: {
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];
w->move(x, y);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
case COMMAND_RESIZE: {
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];
w->resize(width, height);
cmd->result = true;
} else {
cmd->result = false;
}
cmd->done = true;
}
break;
default: {
cmd->result = false;
cmd->done = true;
@@ -111,14 +176,26 @@ void Rktwebview_qt::processCommands()
}
}
void Rktwebview_qt::processJsEventQueue()
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()
@@ -127,7 +204,7 @@ int Rktwebview_qt::nextHandle()
return h;
}
int Rktwebview_qt::rktWebViewCreate(int parent, void (*js_evt_cb)(const char *))
int Rktwebview_qt::rktWebViewCreate(int parent, event_cb_t js_evt_cb)
{
Command c(COMMAND_CREATE);
c.args.push_back(parent);
@@ -168,28 +245,113 @@ result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url)
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);
_command_queue.enqueue(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::set_navigate_failed;
}
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);
_command_queue.enqueue(&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);
_command_queue.enqueue(&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);
_command_queue.enqueue(&c);
while(!c.done) { doEvents(); }
bool r = c.result.toBool();
return r ? result_t::oke : result_t::resize_failed;
}
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);
_command_queue.enqueue(&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)
{
// Inject event handling code for the javascript side
runJs(w, "window.rkt_event_queue = [];\n"
"window.rkt_send_event = function(obj) { window.rkt_event_queue.push(obj); };\n"
"window.rkt_get_events = function() { "
" let q = window.rkt_event_queue; "
" window.rkt_event_queue = [];"
" let json_q = JSON.stringify(q);"
" return json_q;"
"};");
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.
QJsonObject obj;
obj["event"] = "page-loaded";
obj["oke"] = ok;
triggerEvent(w, jsonString(obj));
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()
@@ -211,8 +373,8 @@ void Rktwebview_qt::runJs(rktwebview_t wv, const char *js)
{
if (_views.contains(wv)) {
QString _js(js);
WebViewQt *view = _views[wv]->view();
view->runJs(_js);
WebviewWindow *win = _views[wv];
win->runJs(_js);
}
}
@@ -234,6 +396,8 @@ Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) :
_app = new QApplication(_argc, _argv);
connect(&_process_commands, &QTimer::timeout, this, &Rktwebview_qt::processCommands);
_process_commands.start(10);
connect(&_process_events, &QTimer::timeout, this, &Rktwebview_qt::processJsEventQueues);
_process_events.start(5);
*_handler = nullptr;
}

View File

@@ -3,6 +3,7 @@
#include "rktwebview_qt_global.h"
#include "rktwebview_internal.h"
#include "rktutils.h"
#include <QApplication>
#include <QWebEngineView>
@@ -24,18 +25,23 @@ private:
QApplication *_app;
rktwebview_t _current_handle;
QHash<int, WebviewWindow *> _views;
QHash<int, void(*)(const char *msg)> _view_js_callbacks;
QHash<int, event_cb_t> _view_js_callbacks;
QQueue<Command *> _command_queue;
QTimer _process_commands;
QTimer _process_events;
Rktwebview_qt **_handler;
int _argc;
char *_argv[1];
private:
void runJs(rktwebview_t wv, const char *js);
public slots:
void processCommands();
void processJsEventQueues();
public:
void removeView(int id);
@@ -43,23 +49,26 @@ public:
public:
int nextHandle();
public:
int rktWebViewCreate(int parent, void(*js_evt_cb)(const char *msg));
int rktWebViewCreate(int parent, event_cb_t js_evt_cb);
void rktWebViewClose(int wv);
void rktQuit();
void runJs(rktwebview_t wv, const char *js);
item_t callJs(rktwebview_t wv, const char *js);
void setHtml(rktwebview_t wv, const char *html);
void navigate(rktwebview_t wv, const char *url);
result_t rktOpenDevtools(rktwebview_t wv);
result_t rktSetUrl(rktwebview_t wv, const char *url);
result_t rktSetHtml(rktwebview_t wv, const char *html);
result_t rktRunJs(rktwebview_t wv, const char *js);
result_t rktMove(rktwebview_t wv, int x, int y);
result_t rktResize(rktwebview_t wv, int w, int h);
bool rktValid(rktwebview_t wv);
public:
// Events for the backend
void pageLoaded(rktwebview_t w, bool ok);
void onPageLoad(rktwebview_t w);
public:
void triggerEvent(rktwebview_t wv, const QString &msg);
void triggerEvent(rktwebview_t wv, const EventContainer &e);
public:
void doEvents();

View File

@@ -1,11 +1,13 @@
#include "webviewqt.h"
#include "webviewwindow.h"
#include <QWebEnginePage>
#include <QJsonDocument>
WebViewQt::WebViewQt(int id, QWidget *parent)
: QWebEngineView(parent)
WebViewQt::WebViewQt(int id, WebviewWindow *window)
: QWebEngineView(window)
{
_id = id;
_window = window;
}
int WebViewQt::id() const
@@ -13,8 +15,9 @@ int WebViewQt::id() const
return _id;
}
QString WebViewQt::runJs(const QString &js)
WebviewWindow *WebViewQt::wvWin()
{
return _window;
}

View File

@@ -4,24 +4,25 @@
#include <QWebEngineView>
#include <QTimer>
class WebviewWindow;
class WebViewQt : public QWebEngineView
{
Q_OBJECT
private:
int _id;
WebviewWindow *_window;
public:
int id() const;
public:
QString runJs(const QString &js);
WebviewWindow *wvWin();
signals:
void pageLoaded(WebViewQt *, bool ok);
public:
WebViewQt(int id, QWidget *parent = nullptr);
WebViewQt(int id, WebviewWindow *window);
};

View File

@@ -2,15 +2,33 @@
#include "webviewqt.h"
#include "rktwebview_qt.h"
#include "rktutils.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QWebEngineScriptCollection>
#include <QWebEngineScript>
#include <QMoveEvent>
WebviewWindow::WebviewWindow(QWidget *parent)
: QMainWindow{parent}
{
_view = nullptr;
_must_close = false;
_devtools = nullptr;
connect(&_resize_timer, &QTimer::timeout, this, &WebviewWindow::triggerResize);
connect(&_move_timer, &QTimer::timeout, this, &WebviewWindow::triggerMove);
}
void WebviewWindow::processJsEvents()
{
QWebEnginePage *p = _view->page();
p->runJavaScript("window.getPendingEvents();",
p->runJavaScript("if (window.rkt_get_events) { window.rkt_get_events(); } else { JSON.stringify([]); }",
[this](const QVariant &v) {
QString s = v.toString();
QJsonParseError err;
@@ -19,11 +37,15 @@ void WebviewWindow::processJsEvents()
QJsonArray a = doc.array();
int i, N;
for(i = 0, N = a.size(); i < N; i++) {
QJsonObject evt = a[i].toObject();
QJsonDocument doc_out(evt);
QString msg = doc_out.toJson(QJsonDocument::JsonFormat::Compact);
this->_container->triggerJsEvent(_view->id(), msg);
QJsonObject js_evt = a[i].toObject();
EventContainer e("js-evt");
e["js-evt"] = js_evt;
this->_container->triggerEvent(_view->id(), e);
}
} else {
}
}
);
@@ -31,7 +53,25 @@ void WebviewWindow::processJsEvents()
void WebviewWindow::closeEvent(QCloseEvent *evt)
{
if (_must_close) {
EventContainer e("closed");
_container->triggerEvent(_view->id(), e);
_container->removeView(_view->id());
this->deleteLater();
if (_devtools != nullptr) {
_devtools->deleteLater();
}
} else {
evt->ignore();
EventContainer e("can-close?");
_container->triggerEvent(_view->id(), e);
}
}
void WebviewWindow::closeView()
{
_must_close = true;
close();
}
void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
@@ -41,9 +81,41 @@ void WebviewWindow::addView(WebViewQt *v, Rktwebview_qt *c)
this->setCentralWidget(v);
QWebEnginePage *page = _view->page();
// Inject event handling code for the javascript side
QWebEngineScriptCollection &col = page->scripts();
QWebEngineScript evt_script;
evt_script.setInjectionPoint(QWebEngineScript::DocumentReady);
evt_script.setName("rkt_webview_event_handling");
evt_script.setSourceCode(
"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"
"console.log('We have set:');\n"
"console.log('window.rkt_event_queue:');\n"
"console.log(window.rkt_event_queue);\n"
"console.log('window.rkt_send_event:');\n"
"console.log(window.rkt_send_event);\n"
"console.log('window.rkt_get_events:');\n"
"console.log(window.rkt_get_events);\n"
);
evt_script.setWorldId(QWebEngineScript::ApplicationWorld);
//col.insert(evt_script);
connect(page, &QWebEnginePage::loadFinished, this, [this](bool ok) {
_container->pageLoaded(_view->id(), ok);
});
connect(page, &QWebEnginePage::loadStarted, this, [this]() {
_container->onPageLoad(_view->id());
});
}
WebViewQt *WebviewWindow::view()
@@ -51,9 +123,70 @@ WebViewQt *WebviewWindow::view()
return _view;
}
WebviewWindow::WebviewWindow(QWidget *parent)
: QMainWindow{parent}
void WebviewWindow::runJs(const QString &js)
{
_view = nullptr;
connect(&_process_js_events, &QTimer::timeout, this, &WebviewWindow::processJsEvents);
QWebEnginePage *p = _view->page();
p->runJavaScript(js);
}
void WebviewWindow::openDevTools()
{
_devtools = new QMainWindow(this);
QWebEngineView *devtools_view = new QWebEngineView(_devtools);
_devtools->setCentralWidget(devtools_view);
_devtools->resize(800, 600);
_devtools->show();
_view->page()->setDevToolsPage(devtools_view->page());
connect(_devtools, &WebviewWindow::destroyed, this, [this](QObject *) {
_devtools = nullptr;
});
}
void WebviewWindow::moveEvent(QMoveEvent *event)
{
_x = event->pos().x();
_y = event->pos().y();
_move_timer.setSingleShot(true);
_move_timer.start(500);
}
void WebviewWindow::triggerMove()
{
EventContainer xy("move");
xy["x"] = _x;
xy["y"] = _y;
_container->triggerEvent(_view->id(), xy);
}
void WebviewWindow::resizeEvent(QResizeEvent *event)
{
_w = event->size().width();
_h = event->size().height();
_resize_timer.setSingleShot(true);
_resize_timer.start(500);
}
void WebviewWindow::triggerResize()
{
EventContainer s("resize");
s["w"] = _w;
s["h"] = _h;
_container->triggerEvent(_view->id(), s);
}
void WebviewWindow::showEvent(QShowEvent *event)
{
EventContainer show("show");
_container->triggerEvent(_view->id(), show);
}
void WebviewWindow::hideEvent(QHideEvent *event)
{
if (!_must_close) {
EventContainer hide("hide");
_container->triggerEvent(_view->id(), hide);
}
}

View File

@@ -13,7 +13,16 @@ class WebviewWindow : public QMainWindow
private:
Rktwebview_qt *_container;
WebViewQt *_view;
QTimer _process_js_events;
QMainWindow *_devtools;
QTimer _resize_timer;
QTimer _move_timer;
int _x;
int _y;
int _w;
int _h;
bool _must_close;
public slots:
void processJsEvents();
@@ -22,14 +31,31 @@ protected:
void closeEvent(QCloseEvent *evt);
public:
void addView(WebViewQt *v, Rktwebview_qt *c);
void closeView();
public:
void addView(WebViewQt *v, Rktwebview_qt *c);
WebViewQt *view();
public:
void runJs(const QString &js);
void openDevTools();
public:
explicit WebviewWindow(QWidget *parent = nullptr);
private slots:
void triggerResize();
void triggerMove();
signals:
// QWidget interface
protected:
void moveEvent(QMoveEvent *event);
void resizeEvent(QResizeEvent *event);
void showEvent(QShowEvent *event);
void hideEvent(QHideEvent *event);
};
#endif // WEBVIEWWINDOW_H