initialization and cleanup

This commit is contained in:
2026-03-17 15:42:03 +01:00
parent 7c3b780ae9
commit c50267120a
9 changed files with 209 additions and 216 deletions

View File

@@ -11,6 +11,7 @@
json
racket/string
racket/path
"utils.rkt"
)
(provide rkt-wv
@@ -268,6 +269,10 @@
(define-rktwebview rkt_webview_init
(_fun -> _void))
;RKTWEBVIEW_QT_EXPORT void rkt_webview_cleanup();
(define-rktwebview rkt_webview_cleanup
(_fun -> _void))
;RKTWEBVIEW_QT_EXPORT rkt_wv_context_t rkt_webview_new_context(const char *boilerplate_js,
; const char *optional_server_cert_pem);
(define-rktwebview rkt_webview_new_context
@@ -392,20 +397,23 @@
;; Initialize and start library
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define process-events #t)
(define process-events 'process)
(define (stop-event-processing)
(set! process-events #f))
(set! process-events 'stop)
(while (eq? process-events 'stop)
(sleep 0.001)))
(define (start-event-processing)
(thread (λ ()
(letrec ((f (λ ()
(rkt_webview_process_events 1)
(sleep 0.001)
(if process-events
(if (eq? process-events 'process)
(f)
(begin
(displayln "Stopping event processing")
(set! process-events 'stopped)
'done)))))
(f)))))
@@ -594,7 +602,9 @@
(handle (cdr kv)))
(rkt-webview-close handle)))
open-windows))
(stop-event-processing))
(stop-event-processing)
(rkt_webview_cleanup)
)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Cleanup on exit

View File

@@ -20,67 +20,71 @@ int main(int argc, char *argv[])
_argc = argc;
_argv = argv;
rkt_webview_init();
int k = 0;
while(k < 2) {
rkt_webview_init();
int context = rkt_webview_new_context("console.log('boilerplate!');", nullptr);
int context = rkt_webview_new_context("console.log('boilerplate!');", nullptr);
wv1 = rkt_webview_create(context, 0, eventCb);
wv1 = rkt_webview_create(context, 0, eventCb);
rkt_webview_move(wv1, 200, 300);
rkt_webview_resize(wv1, 800, 600);
rkt_webview_set_url(wv1, "https://wikipedia.org"); //"http://127.0.0.1:8083");
rkt_webview_move(wv1, 200, 300);
rkt_webview_resize(wv1, 800, 600);
rkt_webview_set_url(wv1, "https://wikipedia.org"); //"http://127.0.0.1:8083");
int i = 0;
while(i < 35) {
printf("Waiting...%d\n", i);
rkt_webview_process_events(1000);
k += 1;
int i = 0;
while(i < 10) {
printf("Waiting...%d\n", i);
rkt_webview_process_events(1000);
if (i == 6) {
rkt_webview_open_devtools(wv1);
if (i == 6) {
rkt_webview_open_devtools(wv1);
}
if (i == 3) {
rkt_data_t *r = rkt_webview_call_js(wv1, "{ let a = 7 * 6; console.log('a = ' + a); return a; }");
printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
rkt_webview_free_data(r);
}
if (i == 4) {
rkt_data_t *r = rkt_webview_call_js(wv1, "let el = document.getElementById('hi');el.value = '10';");
printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
rkt_webview_free_data(r);
}
if (i == 6) {
//rkt_data_t *r = rkt_webview_call_js(wv1, "document.body.innerHTML = '<h1>Hi!</h1>'; return document.body.innerHTML;");
//printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
//rkt_webview_free_data(r);
}
if (i == 7) {
result_t r = rkt_webview_message_box(wv1, "This is a title", "This is my message", "This is my submessage", rkt_messagetype_t::yes_no);
}
if (i == 10) {
wv2 = rkt_webview_create(context, wv1, eventCb);
rkt_webview_move(wv2, 400, 200);
rkt_webview_resize(wv2, 800, 600);
rkt_webview_set_url(wv2, "https://127.0.0.1");
}
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 == 15) {
rkt_webview_close(wv2);
}
i += 1;
}
if (i == 3) {
rkt_data_t *r = rkt_webview_call_js(wv1, "{ let a = 7 * 6; console.log('a = ' + a); return a; }");
printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
rkt_webview_free_data(r);
}
if (i == 4) {
rkt_data_t *r = rkt_webview_call_js(wv1, "let el = document.getElementById('hi');el.value = '10';");
printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
rkt_webview_free_data(r);
}
if (i == 6) {
//rkt_data_t *r = rkt_webview_call_js(wv1, "document.body.innerHTML = '<h1>Hi!</h1>'; return document.body.innerHTML;");
//printf("rkt_js_result: %d: %s\n", r->data.js_result.result, r->data.js_result.value);
//rkt_webview_free_data(r);
}
if (i == 7) {
result_t r = rkt_webview_message_box(wv1, "This is a title", "This is my message", "This is my submessage", rkt_messagetype_t::yes_no);
}
if (i == 10) {
wv2 = rkt_webview_create(context, wv1, eventCb);
rkt_webview_move(wv2, 400, 200);
rkt_webview_resize(wv2, 800, 600);
rkt_webview_set_url(wv2, "https://127.0.0.1");
}
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 == 15) {
rkt_webview_close(wv2);
}
i += 1;
rkt_webview_close(wv1);
rkt_webview_cleanup();
}
rkt_webview_close(wv2);
}

View File

@@ -24,11 +24,55 @@ uint64_t current_ms() {
Rktwebview_qt *handler = nullptr;
void rkt_webview_cleanup()
{
if (handler != nullptr) {
rkt_webview_process_events(100);
//QTimer app_quit;
//QObject::connect(&app_quit, &QTimer::timeout, handler->app(), &QApplication::quit, Qt::ConnectionType::QueuedConnection);
//app_quit.setSingleShot(true);
//app_quit.start(250);
//handler->app()->exec();
//fprintf(stderr, "cleanup: handler = %p\n", handler);
// TODO
// Do not delete the QApplication, although it will result in QThreadStorage warnings.
// If the app is deleted, QtWebEngineProfileBuilder->createProfile will memory corrupt.
//handler->deleteApp();
//delete handler;
//handler = nullptr;
//fprintf(stderr, "cleanup: handler = %p\n", handler);
}
}
void rkt_webview_init()
{
if (handler == nullptr) {
handler = new Rktwebview_qt(&handler);
handler->doEvents();
//fprintf(stderr, "init: handler = %p\n", handler);
handler = new Rktwebview_qt();
//fprintf(stderr, "init: handler = %p\n", handler);
}
if (handler->app() == nullptr) {
handler->initApp();
//QTimer app_exit;
//QObject::connect(&app_exit, &QTimer::timeout, handler->app(), []() {
// handler->app()->exit(0);
//}, Qt::ConnectionType::QueuedConnection);
//app_exit.setSingleShot(true);
//app_exit.start(250);
//QApplication *a = handler->app();
//a->exec();
}
}

View File

@@ -89,6 +89,7 @@ typedef struct {
typedef void (*event_cb_t)(rkt_data_t *);
RKTWEBVIEW_QT_EXPORT void rkt_webview_init();
RKTWEBVIEW_QT_EXPORT void rkt_webview_cleanup();
RKTWEBVIEW_QT_EXPORT void rkt_webview_process_events(int for_ms);
RKTWEBVIEW_QT_EXPORT void rkt_webview_free_data(rkt_data_t *d);
RKTWEBVIEW_QT_EXPORT rkt_data_t *rkt_webview_version();

View File

@@ -29,11 +29,6 @@ static inline char *copyString(const char *s)
void Rktwebview_qt::processCommand(Command *cmd)
{
switch(cmd->cmd) {
case COMMAND_QUIT: {
_app->quit();
cmd->done = true;
}
break;
case COMMAND_CREATE: {
rkt_wv_context_t context = cmd->args[0].toInt();
rktwebview_t parent = cmd->args[1].toInt();
@@ -80,6 +75,11 @@ void Rktwebview_qt::processCommand(Command *cmd)
_views.remove(wv);
w->closeView();
cmd->result = true;
while(w->isVisible()) {
doEvents();
}
_view_js_callbacks.remove(wv);
delete w;
} else {
cmd->result = false;
}
@@ -630,7 +630,7 @@ result_t Rktwebview_qt::fileDlg(rktwebview_t w, const char *title, const char *b
QStringList l = dlg->selectedFiles();
QString file;
if (l.size() > 0) {
file = dlg->selectedFiles().first();
file = l[0];
}
EventContainer e(evt_ok);
@@ -817,19 +817,41 @@ void Rktwebview_qt::triggerEvent(rktwebview_t wv, const QString &msg)
}
}
void Rktwebview_qt::rktQuit()
void Rktwebview_qt::execApp()
{
QList<int> keys = _views.keys();
int i;
for(i = 0; i < keys.size(); i++) {
int view_handle = keys[i];
rktWebViewClose(view_handle);
}
_app->exec();
}
QApplication *Rktwebview_qt::app()
{
return _app;
}
void Rktwebview_qt::deleteApp()
{
delete _app;
_app = nullptr;
}
void Rktwebview_qt::initApp()
{
_app = new QApplication(_argc, _argv);
// 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);
}
);
Command c(COMMAND_QUIT);
postCommand(&c);
while(!c.done) { doEvents(); }
}
void Rktwebview_qt::runJs(rktwebview_t wv, const char *js)
@@ -889,35 +911,17 @@ void Rktwebview_qt::stopEventloop()
}
Rktwebview_qt::Rktwebview_qt(Rktwebview_qt **handler) :
QObject()
Rktwebview_qt::Rktwebview_qt() : QObject()
{
_argc = 1;
_argv[0] = const_cast<char *>("Rktwebview_qt");
_context_counter = 0;
_current_handle = 0;
_handler = handler;
_evt_loop_depth = 0;
_app = new QApplication(_argc, _argv);
// 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;
_app = nullptr;
}
Rktwebview_qt::~Rktwebview_qt()
@@ -934,4 +938,6 @@ Rktwebview_qt::~Rktwebview_qt()
QWebEngineProfile *p = _contexts[c_keys[i]];
delete p;
}
delete _app;
}

View File

@@ -38,8 +38,6 @@ private:
int _evt_loop_depth;
QTimer _evt_loop_timer;
Rktwebview_qt **_handler;
int _argc;
char *_argv[1];
@@ -57,6 +55,12 @@ private:
result_t fileDlg(rktwebview_t w, const char *title, const char *base, const char *filters, QFileDialog::FileMode mode, QFileDialog::AcceptMode am, QString evt_ok, QString evt_cancel);
public:
void execApp();
QApplication *app();
void deleteApp();
void initApp();
protected:
void customEvent(QEvent *event);
@@ -65,12 +69,12 @@ public:
public:
int nextHandle();
public:
rkt_wv_context_t newContext(const char *boilerplate_js, const char *optional_server_cert_pem);
int rktWebViewCreate(rkt_wv_context_t context, rktwebview_t parent, event_cb_t js_evt_cb);
void rktWebViewClose(int wv);
void rktSetOUToken(rktwebview_t wv, const char *ou_token);
void rktQuit();
result_t rktOpenDevtools(rktwebview_t wv);
result_t rktSetUrl(rktwebview_t wv, const char *url);
@@ -113,7 +117,7 @@ public:
void runCommandThread();
public:
Rktwebview_qt(Rktwebview_qt **handler);
Rktwebview_qt();
~Rktwebview_qt();
};

View File

@@ -179,6 +179,7 @@ void WebviewWindow::closeEvent(QCloseEvent *evt)
_view->deleteLater();
this->deleteLater();
if (_devtools != nullptr) {
_devtools->close();
_devtools->deleteLater();
}
} else {

View File

@@ -12,36 +12,24 @@ Qt-based webviews from Racket through the FFI.
The public interface is declared in @tt{rktwebview.h}. Its implementation in
@tt{rktwebview.cpp} mainly forwards requests to the internal Qt runtime.
The API consists of:
@itemlist[#:style 'compact
@item{initialization and event processing}
@item{version and memory-management helpers}
@item{browser-context management}
@item{window creation and lifecycle control}
@item{navigation and content loading}
@item{JavaScript execution}
@item{window-state manipulation}
@item{native dialogs}
]
The API consists of initialization and event processing, version and memory-management utility
functions, http(s)-context management, window management, navigation and content loading,
JavaScript execution and native dialogs.
@section{Basic Types}
@subsection{Window and Context Handles}
The API uses integer handles for windows and browser contexts.
The API uses integer handles for windows and contexts.A value of type @tt{rktwebview_t} identifies
a single webview window and of type @tt{rkt_wv_context_t} identifies a http(s) context. Both
are typedefs of @tt{int}.
@verbatim|{
typedef int rktwebview_t;
typedef int rkt_wv_context_t;
}|
@subsection{Returned data, data for events and resultcodes}
A value of type @tt{rktwebview_t} identifies a single webview window.
A value of type @tt{rkt_wv_context_t} identifies a browser context.
@subsection{Returned Data Objects}
Some API calls return a pointer to a heap-allocated @tt{rkt_data_t} value.
The following type is used for communication of data, version information and events.
All possible data that is communicated is assembled in this one type that will be
allocated by the API and must be released by the API user, using a call to
@tt{rkt_webview_free_data()}.
@verbatim|{
typedef enum {
@@ -60,9 +48,8 @@ typedef struct {
} rkt_data_t;
}|
The @tt{kind} field determines which member of the union is valid.
@subsubsection{Version Data}
The @tt{kind} field determines which member of the union is valid. A version field
is a struct of integer numbers:
@verbatim|{
typedef struct {
@@ -75,9 +62,9 @@ typedef struct {
} rkt_version_t;
}|
Version data is returned by @tt{rkt_webview_version}.
@subsubsection{Event Data}
Events are always for webview windows and consist of the webview handle along with
an event. This event is always a Utf-8 JSON string. In this JSON string, the @tt{"event"}}
item holds the name of the event that is triggered.
@verbatim|{
typedef struct {
@@ -86,12 +73,8 @@ typedef struct {
} rkt_event_t;
}|
Event data is delivered to the registered callback when the backend emits an
event for a window.
The @tt{event} field is a UTF-8 JSON string allocated by the native side.
@subsubsection{JavaScript Result Data}
Returned data (mostly as a result of Javascript calls) consists of a result code and
a JSON encoded value. See following struct.
@verbatim|{
typedef struct {
@@ -100,84 +83,27 @@ typedef struct {
} rkt_js_result_t;
}|
JavaScript result data is returned by @tt{rkt_webview_call_js}.
Many functions return a value of type @tt{result_t}. The possible enumeration values
can be found in @tt{rktwebview.h}. Most important are the values @tt{no_result_yet}, which
is for internal use and equals @tt{-1}; @tt{oke = 0}, which tells that everything went alright,
and all values @tt{>0} indicate some kind of failure.
The @tt{value} field contains a UTF-8 JSON string describing the result of the
JavaScript evaluation.
@subsection{Window state}
@subsection{Result Codes}
Window state is represented by @tt{window_state_t}. The following values are used:
@tt{invalid = -1}, represents an invalid window state, @tt{normal = 0x00} and @tt{normal_active = 0x10},
window shown in normal state (0x00). All windows that are active, i.e. have focus for user input, will
have bit 5 set to 1. Other states: @tt{minimized = 0x01}, @tt{maximized = 0x02} or @tt{0x12},
@tt{hidden = 0x03}.
Many functions return a value of type @tt{result_t}.
@subsection{Messagebox types}
@verbatim|{
typedef enum {
no_result_yet = -1,
oke = 0,
set_html_failed = 1,
set_navigate_failed = 2,
eval_js_failed = 3,
no_devtools_on_platform = 4,
no_delegate_for_context = 5,
webview_missing_dependency = 6,
webview_canceled = 7,
webview_invalid_state = 8,
webview_invalid_argument = 9,
webview_unspecified = 10,
webview_dispatch_failed = 11,
move_failed = 12,
resize_failed = 13,
choose_dir_failed = 14,
open_file_failed = 15,
save_file_failed = 16,
failed = 17
} result_t;
}|
Message boxes use the type @tt{rkt_messagetype_t} to indicate the type of message to present:
@tt{info = 1} for information, @tt{error = 2} for error messages, @tt{warning = 3} for warnings,
@tt{yes_no = 4} for questions that can be answered by 'yes' or 'no' and @tt{oke_cancel} for messages
that can be answered by 'oke' or 'cancel'.
In the current implementation, the most commonly returned values are:
@itemlist[#:style 'compact
@item{@tt{oke} for success}
@item{@tt{set_html_failed} for @tt{rkt_webview_set_html}}
@item{@tt{set_navigate_failed} for @tt{rkt_webview_set_url}}
@item{@tt{eval_js_failed} for JavaScript execution failure}
@item{@tt{move_failed} for failed move requests}
@item{@tt{resize_failed} for failed resize requests}
@item{@tt{failed} for general window-operation failure}
]
@subsection{Window States}
Window state is represented by @tt{window_state_t}.
@verbatim|{
typedef enum {
invalid = -1,
normal = 0,
minimized = 1,
maximized = 2,
hidden = 3,
normal_active = 16,
maximized_active = 18
} window_state_t;
}|
This type is returned by @tt{rkt_webview_window_state}.
@subsection{Message Box Types}
Message boxes use the type @tt{rkt_messagetype_t}.
@verbatim|{
typedef enum {
info = 1,
error = 2,
warning = 3,
yes_no = 4,
oke_cancel = 5
} rkt_messagetype_t;
}|
@subsection{Event Callback Type}
@subsection{Callback functions for events}
A window is created with an event callback of the following type:
@@ -185,13 +111,10 @@ A window is created with an event callback of the following type:
typedef void (*event_cb_t)(rkt_data_t *);
}|
The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is
@tt{event}.
The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is @tt{event}. The callback
argument must eventually be released by calling @tt{rkt_webview_free_data}.
The callback argument must eventually be released by calling
@tt{rkt_webview_free_data}.
@subsection{Memory Ownership}
@subsection{Memory management}
The following functions return heap-allocated @tt{rkt_data_t *} values: