#lang scribble/manual @title{Public API Specification for the Qt–Backed WebView Bridge} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @italic{For use with Racket FFI} This document defines the public C-level API of the Qt-based WebView bridge. It targets Racket developers integrating the C library via the Racket FFI. It specifies the interface, result values, memory ownership rules, event formats, expected behavior, and recommended calling patterns. Internal architecture is documented separately. @section{Calling Convention and Structure} All functions are C functions with normal C linkage. Window handles are simple integers that uniquely identify native windows. @itemlist[#:style 'compact @item{Header: @tt{rktwebview.h}} @item{ABI: plain C, no name mangling} @item{Handles remain valid from creation until the window is fully closed} ] @section{Opaque and Helper Types} @subsection{Window Handle} @itemlist[#:style 'compact @item{@tt{typedef int rktwebview_t}: Valid for any window created by @tt{rkt_webview_create} until closed.} ] @subsection{Event Callback Types} @itemlist[#:style 'compact @item{@tt{typedef struct \{ rktwebview_t wv; char* event; \} rkt_event_t}: JSON event payload for the host (UTF‐8).} @item{@tt{typedef void (*event_cb_t)(rkt_event_t*)}: Host-supplied callback for event delivery.} ] @subsection{JS Result Structure} @itemlist[#:style 'compact @item{@tt{typedef struct \{ result_t result; char* value; \} rkt_js_result_t}: @tt{result} is always @tt{oke} or @tt{eval_js_failed}; @tt{value} holds a UTF‐8 JSON string describing the JS outcome.} ] @section{Enumerations} @subsection{result_t} @itemlist[#:style 'compact @item{@tt{oke = 0}} @item{@tt{set_html_failed = 1}} @item{@tt{set_navigate_failed = 2}} @item{@tt{eval_js_failed = 3}} @item{@tt{move_failed = 12}} @item{@tt{resize_failed = 13}} @item{@tt{choose_dir_failed = 14}} @item{@tt{open_file_failed = 15}} @item{@tt{save_file_failed = 16}} @item{@tt{failed = 17}} ] @subsection{window_state_t} @itemlist[#:style 'compact @item{@tt{invalid = -1}} @item{@tt{normal = 0}} @item{@tt{minimized = 1}} @item{@tt{maximized = 2}} @item{@tt{hidden = 3}} @item{@tt{normal_active = 16}} @item{@tt{maximized_active = 18}} ] @section{Threading Model} @itemlist[#:style 'compact @item{All GUI work occurs on the Qt GUI thread.} @item{Public calls block until the posted internal command has completed.} @item{The host is expected to drive Qt by calling @tt{rkt_webview_process_events}.} ] @section{Memory Ownership} @itemlist[#:style 'compact @item{@tt{rkt_event_t*} must be freed using @tt{rkt_webview_destroy_event}.} @item{@tt{rkt_js_result_t*} must be freed using @tt{rkt_webview_destroy_js_result}.} @item{All returned strings are UTF‐8 and owned by the library until destroyed with the corresponding function.} ] @section{Function Reference (C signatures)} The following are literal C signatures with behavioral descriptions. @subsection{Initialization and Event Pumping} @codeblock{ void rkt_webview_init(void); } Initializes Qt and internal state. Safe to call multiple times (subsequent calls are no-ops). @codeblock{ void rkt_webview_process_events(int for_ms); } Pumps the Qt event loop for approximately @tt{for_ms} milliseconds. Idea for implementation in racket: Use a @emph{cooperative thread} with @emph{time slicing}. A practical starting point: @itemlist[#:style 'compact @item{~3 ms Qt pumping per iteration: @tt{(rkt_webview_process_events 3)}} @item{~10 ms Racket time (scheduler): @tt{(sleep 0.01)}} ] Tune these values for your application. Example: @verbatim|{ (define running? (box #t)) (define (pump-loop) (let loop () (when (unbox running?) (rkt_webview_process_events 3) ; ~3 ms Qt (sleep 0.01) ; ~10 ms for Racket scheduler (loop)))) (define pump-thread (thread pump-loop)) ;; ... later on shutdown: (set-box! running? #f) (thread-wait pump-thread) }| @subsection{Window Lifecycle} @codeblock{ rktwebview_t rkt_webview_create(rktwebview_t parent, event_cb_t cb); } Creates a window, optionally modal if @tt{parent} exists. Registers @tt{cb}. The window is shown before returning. @codeblock{ void rkt_webview_close(rktwebview_t wv); } Requests closure of @tt{wv}. Behavior: @itemlist[#:style 'compact @item{If the user tries to close via the window manager (close button, Alt–F4, etc.), the library intercepts it and emits @tt{"can-close?"}. The window remains open.} @item{To actually close after receiving @tt{"can-close?"}, the host must call @tt{rkt_webview_close}.} @item{When the final close occurs, @tt{"closed"} is emitted and the handle becomes invalid.} ] @codeblock{ bool rkt_webview_valid(rktwebview_t wv); } Returns whether @tt{wv} refers to an active window. @subsection{Navigation and Content} @codeblock{ result_t rkt_webview_set_url(rktwebview_t wv, const char* url); } Navigate to @tt{url}. Returns @tt{oke} or @tt{set_navigate_failed}. @codeblock{ result_t rkt_webview_set_html(rktwebview_t wv, const char* html); } Set page content from @tt{html}. Returns @tt{oke} or @tt{set_html_failed}. @codeblock{ result_t rkt_webview_set_title(rktwebview_t wv, const char* title); } Set the native window title. Returns @tt{oke} or @tt{failed}. @subsection{JavaScript Execution} @codeblock{ result_t rkt_webview_run_js(rktwebview_t wv, const char* js); } Fire-and-forget JavaScript execution. Returns @tt{oke} or @tt{eval_js_failed}. @codeblock{ rkt_js_result_t* rkt_webview_call_js(rktwebview_t wv, const char* js); } Synchronous JS call. Guarantees: @itemlist[#:style 'compact @item{@tt{result} is @tt{oke} or @tt{eval_js_failed}.} @item{@tt{value} is JSON: @tt{{"oke": , "result": , "exn": }}.} ] Must be freed with @tt{rkt_webview_destroy_js_result()}. @codeblock{ result_t rkt_webview_destroy_js_result(rkt_js_result_t* r); } Frees JS result memory. Returns @tt{oke}. @subsection{Developer Tools} @codeblock{ result_t rkt_webview_open_devtools(rktwebview_t wv); } Open devtools. Returns @tt{oke} or @tt{no_devtools_on_platform} (platform-dependent). @subsection{Window Geometry and Visibility} @codeblock{ result_t rkt_webview_move(rktwebview_t wv, int x, int y); } Waits until move is acknowledged. Returns @tt{oke} or @tt{move_failed}. @codeblock{ result_t rkt_webview_resize(rktwebview_t wv, int w, int h); } Waits until resize is acknowledged. Returns @tt{oke} or @tt{resize_failed}. @codeblock{ result_t rkt_webview_hide(rktwebview_t wv); } @codeblock{ result_t rkt_webview_show(rktwebview_t wv); } @codeblock{ result_t rkt_webview_show_normal(rktwebview_t wv); } @codeblock{ result_t rkt_webview_present(rktwebview_t wv); } @codeblock{ result_t rkt_webview_maximize(rktwebview_t wv); } @codeblock{ result_t rkt_webview_minimize(rktwebview_t wv); } All return @tt{oke} or @tt{failed} (when the operation cannot be applied). @codeblock{ window_state_t rkt_webview_window_state(rktwebview_t wv); } Returns a simplified window state. @subsection{Event & Dialog Systems} @codeblock{ result_t rkt_webview_destroy_event(rkt_event_t* e); } Frees event memory. Returns @tt{oke}. @subsection{Native Dialogs} @codeblock{ rkt_js_result_t* rkt_webview_choose_dir(rktwebview_t wv, const char* title, const char* base_dir); } Returns JSON: @itemlist[#:style 'compact @item{@tt{"state":"choosen","dir":"..."}} @item{@tt{"state":"canceled","dir":"..."}} ] Must be freed with @tt{rkt_webview_destroy_js_result}. @codeblock{ rkt_js_result_t* rkt_webview_file_open(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts); } @codeblock{ rkt_js_result_t* rkt_webview_file_save(rktwebview_t wv, const char* title, const char* base_dir, const char* permitted_exts); } Return JSON: @itemlist[#:style 'compact @item{@tt{"state":"choosen","file":"...","used-filter":"..."}} @item{@tt{"state":"canceled","file":"","used-filter":"..."}} ] Must be freed with @tt{rkt_webview_destroy_js_result}. @subsection{Security} @codeblock{ void rkt_webview_set_ou_token(rktwebview_t wv, const char* token); } This function configures a per-window "OU acceptance token" that affects how certificate errors are handled inside the internal @tt{QWebEnginePage}. When a TLS certificate error occurs, the internal page examines the certificate chain. If all the following conditions are met: @itemlist[#:style 'compact @item{the certificate is self-signed,} @item{the certificate contains an Organizational Unit (OU) field,} @item{the OU value exactly matches the token previously set with @tt{rkt_webview_set_ou_token},} ] then the certificate is programmatically accepted and the load is allowed to continue. This mechanism is intended for controlled environments (e.g., local development servers or private deployments) where a known self-signed certificate is used and its OU value is part of the trust boundary. Note: starting with Qt 6.10, @tt{QWebEnginePage} introduces an API for providing a specific certificate to be accepted directly by the page. However, this API is very new ("bleeding edge") and not yet used or relied upon in this WebView bridge. @section{Event JSON Format} @itemlist[#:style 'compact @item{@tt{"event":"page-loaded","oke": }} @item{@tt{"event":"show"}} @item{@tt{"event":"hide"}} @item{@tt{"event":"move","x": ,"y": }} @item{@tt{"event":"resize","w": ,"h": }} @item{@tt{"event":"can-close?"}} @item{@tt{"event":"closed"}} @item{@tt{"event":"js-evt","js-evt": }} ] @section{Typical Usage Pattern} @itemlist[#:style 'compact @item{Initialize once.} @item{Create windows.} @item{Run a cooperative time-sliced pump loop (e.g., ~3 ms Qt, ~10 ms Racket).} @item{Invoke JS run/call functions.} @item{Handle and free events.} @item{Close windows explicitly.} ] @section{Summary} This API provides a stable, simple interface for embedding a Qt WebEngine webview inside a Racket program via FFI. The API is synchronous, uses compact JSON for results, and defines clear memory ownership rules. ``