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: