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

View File

@@ -20,6 +20,8 @@ int main(int argc, char *argv[])
_argc = argc; _argc = argc;
_argv = argv; _argv = argv;
int k = 0;
while(k < 2) {
rkt_webview_init(); rkt_webview_init();
int context = rkt_webview_new_context("console.log('boilerplate!');", nullptr); int context = rkt_webview_new_context("console.log('boilerplate!');", nullptr);
@@ -30,8 +32,9 @@ int main(int argc, char *argv[])
rkt_webview_resize(wv1, 800, 600); rkt_webview_resize(wv1, 800, 600);
rkt_webview_set_url(wv1, "https://wikipedia.org"); //"http://127.0.0.1:8083"); rkt_webview_set_url(wv1, "https://wikipedia.org"); //"http://127.0.0.1:8083");
k += 1;
int i = 0; int i = 0;
while(i < 35) { while(i < 10) {
printf("Waiting...%d\n", i); printf("Waiting...%d\n", i);
rkt_webview_process_events(1000); rkt_webview_process_events(1000);
@@ -81,6 +84,7 @@ int main(int argc, char *argv[])
i += 1; i += 1;
} }
rkt_webview_close(wv2); rkt_webview_close(wv1);
rkt_webview_cleanup();
}
} }

View File

@@ -24,11 +24,55 @@ uint64_t current_ms() {
Rktwebview_qt *handler = nullptr; 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() void rkt_webview_init()
{ {
if (handler == nullptr) { if (handler == nullptr) {
handler = new Rktwebview_qt(&handler); //fprintf(stderr, "init: handler = %p\n", handler);
handler->doEvents(); 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 *); typedef void (*event_cb_t)(rkt_data_t *);
RKTWEBVIEW_QT_EXPORT void rkt_webview_init(); 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_process_events(int for_ms);
RKTWEBVIEW_QT_EXPORT void rkt_webview_free_data(rkt_data_t *d); RKTWEBVIEW_QT_EXPORT void rkt_webview_free_data(rkt_data_t *d);
RKTWEBVIEW_QT_EXPORT rkt_data_t *rkt_webview_version(); 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) void Rktwebview_qt::processCommand(Command *cmd)
{ {
switch(cmd->cmd) { switch(cmd->cmd) {
case COMMAND_QUIT: {
_app->quit();
cmd->done = true;
}
break;
case COMMAND_CREATE: { case COMMAND_CREATE: {
rkt_wv_context_t context = cmd->args[0].toInt(); rkt_wv_context_t context = cmd->args[0].toInt();
rktwebview_t parent = cmd->args[1].toInt(); rktwebview_t parent = cmd->args[1].toInt();
@@ -80,6 +75,11 @@ void Rktwebview_qt::processCommand(Command *cmd)
_views.remove(wv); _views.remove(wv);
w->closeView(); w->closeView();
cmd->result = true; cmd->result = true;
while(w->isVisible()) {
doEvents();
}
_view_js_callbacks.remove(wv);
delete w;
} else { } else {
cmd->result = false; 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(); QStringList l = dlg->selectedFiles();
QString file; QString file;
if (l.size() > 0) { if (l.size() > 0) {
file = dlg->selectedFiles().first(); file = l[0];
} }
EventContainer e(evt_ok); 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(); _app->exec();
int i; }
for(i = 0; i < keys.size(); i++) {
int view_handle = keys[i]; QApplication *Rktwebview_qt::app()
rktWebViewClose(view_handle); {
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) 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; _argc = 1;
_argv[0] = const_cast<char *>("Rktwebview_qt"); _argv[0] = const_cast<char *>("Rktwebview_qt");
_context_counter = 0; _context_counter = 0;
_current_handle = 0; _current_handle = 0;
_handler = handler;
_evt_loop_depth = 0; _evt_loop_depth = 0;
_app = new QApplication(_argc, _argv);
// See Qt 6.10 remark at doEvents. _app = nullptr;
//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;
} }
Rktwebview_qt::~Rktwebview_qt() Rktwebview_qt::~Rktwebview_qt()
@@ -934,4 +938,6 @@ Rktwebview_qt::~Rktwebview_qt()
QWebEngineProfile *p = _contexts[c_keys[i]]; QWebEngineProfile *p = _contexts[c_keys[i]];
delete p; delete p;
} }
delete _app;
} }

View File

@@ -38,8 +38,6 @@ private:
int _evt_loop_depth; int _evt_loop_depth;
QTimer _evt_loop_timer; QTimer _evt_loop_timer;
Rktwebview_qt **_handler;
int _argc; int _argc;
char *_argv[1]; 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); 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: protected:
void customEvent(QEvent *event); void customEvent(QEvent *event);
@@ -65,12 +69,12 @@ public:
public: public:
int nextHandle(); int nextHandle();
public: public:
rkt_wv_context_t newContext(const char *boilerplate_js, const char *optional_server_cert_pem); 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); int rktWebViewCreate(rkt_wv_context_t context, rktwebview_t parent, event_cb_t js_evt_cb);
void rktWebViewClose(int wv); void rktWebViewClose(int wv);
void rktSetOUToken(rktwebview_t wv, const char *ou_token); void rktSetOUToken(rktwebview_t wv, const char *ou_token);
void rktQuit();
result_t rktOpenDevtools(rktwebview_t wv); result_t rktOpenDevtools(rktwebview_t wv);
result_t rktSetUrl(rktwebview_t wv, const char *url); result_t rktSetUrl(rktwebview_t wv, const char *url);
@@ -113,7 +117,7 @@ public:
void runCommandThread(); void runCommandThread();
public: public:
Rktwebview_qt(Rktwebview_qt **handler); Rktwebview_qt();
~Rktwebview_qt(); ~Rktwebview_qt();
}; };

View File

@@ -179,6 +179,7 @@ void WebviewWindow::closeEvent(QCloseEvent *evt)
_view->deleteLater(); _view->deleteLater();
this->deleteLater(); this->deleteLater();
if (_devtools != nullptr) { if (_devtools != nullptr) {
_devtools->close();
_devtools->deleteLater(); _devtools->deleteLater();
} }
} else { } 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 The public interface is declared in @tt{rktwebview.h}. Its implementation in
@tt{rktwebview.cpp} mainly forwards requests to the internal Qt runtime. @tt{rktwebview.cpp} mainly forwards requests to the internal Qt runtime.
The API consists of: The API consists of initialization and event processing, version and memory-management utility
functions, http(s)-context management, window management, navigation and content loading,
@itemlist[#:style 'compact JavaScript execution and native dialogs.
@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}
]
@section{Basic Types} @section{Basic Types}
@subsection{Window and Context Handles} @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|{ @subsection{Returned data, data for events and resultcodes}
typedef int rktwebview_t;
typedef int rkt_wv_context_t;
}|
A value of type @tt{rktwebview_t} identifies a single webview window. The following type is used for communication of data, version information and events.
A value of type @tt{rkt_wv_context_t} identifies a browser context. 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
@subsection{Returned Data Objects} @tt{rkt_webview_free_data()}.
Some API calls return a pointer to a heap-allocated @tt{rkt_data_t} value.
@verbatim|{ @verbatim|{
typedef enum { typedef enum {
@@ -60,9 +48,8 @@ typedef struct {
} rkt_data_t; } rkt_data_t;
}| }|
The @tt{kind} field determines which member of the union is valid. The @tt{kind} field determines which member of the union is valid. A version field
is a struct of integer numbers:
@subsubsection{Version Data}
@verbatim|{ @verbatim|{
typedef struct { typedef struct {
@@ -75,9 +62,9 @@ typedef struct {
} rkt_version_t; } rkt_version_t;
}| }|
Version data is returned by @tt{rkt_webview_version}. 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"}}
@subsubsection{Event Data} item holds the name of the event that is triggered.
@verbatim|{ @verbatim|{
typedef struct { typedef struct {
@@ -86,12 +73,8 @@ typedef struct {
} rkt_event_t; } rkt_event_t;
}| }|
Event data is delivered to the registered callback when the backend emits an Returned data (mostly as a result of Javascript calls) consists of a result code and
event for a window. a JSON encoded value. See following struct.
The @tt{event} field is a UTF-8 JSON string allocated by the native side.
@subsubsection{JavaScript Result Data}
@verbatim|{ @verbatim|{
typedef struct { typedef struct {
@@ -100,84 +83,27 @@ typedef struct {
} rkt_js_result_t; } 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 @subsection{Window state}
JavaScript evaluation.
@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|{ Message boxes use the type @tt{rkt_messagetype_t} to indicate the type of message to present:
typedef enum { @tt{info = 1} for information, @tt{error = 2} for error messages, @tt{warning = 3} for warnings,
no_result_yet = -1, @tt{yes_no = 4} for questions that can be answered by 'yes' or 'no' and @tt{oke_cancel} for messages
oke = 0, that can be answered by 'oke' or 'cancel'.
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;
}|
In the current implementation, the most commonly returned values are: @subsection{Callback functions for events}
@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}
A window is created with an event callback of the following type: 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 *); typedef void (*event_cb_t)(rkt_data_t *);
}| }|
The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is The callback receives a pointer to a @tt{rkt_data_t} whose @tt{kind} is @tt{event}. The callback
@tt{event}. argument must eventually be released by calling @tt{rkt_webview_free_data}.
The callback argument must eventually be released by calling @subsection{Memory management}
@tt{rkt_webview_free_data}.
@subsection{Memory Ownership}
The following functions return heap-allocated @tt{rkt_data_t *} values: The following functions return heap-allocated @tt{rkt_data_t *} values: