diff --git a/private/lib/linux/x86_64/librktwebview_qt.so b/private/lib/linux/x86_64/librktwebview_qt.so
index 9ada80f..d388aa2 100755
Binary files a/private/lib/linux/x86_64/librktwebview_qt.so and b/private/lib/linux/x86_64/librktwebview_qt.so differ
diff --git a/private/racket-webview-qt.rkt b/private/racket-webview-qt.rkt
index 4c4ed9d..fb3be7a 100644
--- a/private/racket-webview-qt.rkt
+++ b/private/racket-webview-qt.rkt
@@ -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
diff --git a/rktwebview_qt/main.cpp b/rktwebview_qt/main.cpp
index 6555cad..5b6763d 100644
--- a/rktwebview_qt/main.cpp
+++ b/rktwebview_qt/main.cpp
@@ -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 = '
Hi!
'; 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 = 'Hi!
'; 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);
-
}
diff --git a/rktwebview_qt/rktwebview.cpp b/rktwebview_qt/rktwebview.cpp
index 3fe9146..c01240d 100644
--- a/rktwebview_qt/rktwebview.cpp
+++ b/rktwebview_qt/rktwebview.cpp
@@ -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();
}
}
diff --git a/rktwebview_qt/rktwebview.h b/rktwebview_qt/rktwebview.h
index 50c3cbc..c661221 100644
--- a/rktwebview_qt/rktwebview.h
+++ b/rktwebview_qt/rktwebview.h
@@ -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();
diff --git a/rktwebview_qt/rktwebview_qt.cpp b/rktwebview_qt/rktwebview_qt.cpp
index f383536..a9d07dd 100644
--- a/rktwebview_qt/rktwebview_qt.cpp
+++ b/rktwebview_qt/rktwebview_qt.cpp
@@ -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 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("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;
}
diff --git a/rktwebview_qt/rktwebview_qt.h b/rktwebview_qt/rktwebview_qt.h
index d9a9f6d..2901364 100644
--- a/rktwebview_qt/rktwebview_qt.h
+++ b/rktwebview_qt/rktwebview_qt.h
@@ -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();
};
diff --git a/rktwebview_qt/webviewwindow.cpp b/rktwebview_qt/webviewwindow.cpp
index e146621..4c3ab37 100644
--- a/rktwebview_qt/webviewwindow.cpp
+++ b/rktwebview_qt/webviewwindow.cpp
@@ -179,6 +179,7 @@ void WebviewWindow::closeEvent(QCloseEvent *evt)
_view->deleteLater();
this->deleteLater();
if (_devtools != nullptr) {
+ _devtools->close();
_devtools->deleteLater();
}
} else {
diff --git a/scrbl/rktwebview-api.scrbl b/scrbl/rktwebview-api.scrbl
index 1b348fe..d8dd45f 100644
--- a/scrbl/rktwebview-api.scrbl
+++ b/scrbl/rktwebview-api.scrbl
@@ -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: