#lang scribble/manual @defmodule{racket-webview/racket-webview-qt} @title{Racket FFI Interface for @tt{rktwebview_qt}} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @section{Overview} The module @tt{racket-webview-qt.rkt} provides a Racket FFI wrapper around the native @tt{rktwebview_qt} library. It loads the shared library, initializes the native runtime, and exposes Racket functions for creating and controlling webview windows. If the Qt backend is available locally, it is loaded directly. Otherwise the module attempts to resolve and download the backend. If that is not possible, the module continues in a degraded mode in which a limited subset of the FFI entry points will only display a warning and perform a no-op. All others will fail. The wrapper translates the low-level C interface into a Racket-oriented API based on structures, callbacks, and ordinary Racket values. The module provides: @itemlist[#:style 'compact @item{creation of HTTP(S) contexts} @item{creation and management of webview windows} @item{navigation and HTML loading} @item{JavaScript execution} @item{window geometry and visibility control} @item{native dialogs} @item{asynchronous event delivery} @item{version and cleanup utilities} ] @section{Requirements} The native backend requires Qt version @tt{6.10.2} or newer. The shared library @tt{rktwebview_qt} must therefore be built against Qt @tt{6.10.2} or a compatible later release. Earlier Qt versions are not supported. @section{Backend Availability} The module first checks whether the expected @tt{racket-webview-qt} backend is already installed. If it is not installed, the module attempts to resolve the configured download site. If the site can be resolved and the configured archive is downloadable, the backend is downloaded automatically. If the download site cannot be resolved, if no archive is available for the current operating system and machine architecture, or if the download fails, the module does not immediately abort module loading. Instead it switches to a degraded mode in which native FFI loading is disabled. In that degraded mode, a textual reason is stored internally and selected FFI entry points are replaced by fallback implementations. When the backend cannot be loaded, the module defines fallback implementations for missing FFI entry points through @racket[define-ffi-definer] and @racket[#:default-make-fail]. These fallbacks behave in two different ways. For a small set of initialization and shutdown functions, a non-failing fallback is installed: @itemlist[#:style 'compact @item{@racket[rkt_webview_env] returns @racket[#t]} @item{@racket[rkt_webview_events_waiting] returns @racket[0]} @item{@racket[rkt_webview_init] returns @racket[#t]} @item{@racket[rkt_webview_cleanup] returns @racket[#t]}] All other missing FFI functions raise an exception when called. Fallback warnings are emitted at most once per function. If native loading was disabled because the backend was unavailable, the warning message includes the recorded reason. If native loading was enabled but a specific symbol could not be loaded from the library, the error names the library file. @section{Module Initialization} Loading the module performs several initialization steps automatically. @itemlist[#:style 'compact @item{determines the operating system and architecture} @item{sets Qt runtime environment variables} @item{loads the @tt{rktwebview_qt} shared library} @item{initializes the native runtime} @item{starts a background thread that processes native events} ] Currently the wrapper supports the following platforms: @itemlist[#:style 'compact @item{@tt{'linux}} @item{@tt{'windows}} ] If the current system is unsupported, loading the module raises an error. @section{Data Model} @subsection{The @tt{rkt-wv} Structure} Each webview window is represented by a transparent Racket structure. @defstruct*[rkt-wv ([win exact-integer?] [evt-queue any/c] [callback procedure?] [valid boolean?] [close-callback procedure?])]{ Represents a webview instance managed by the Racket wrapper. Fields: @itemlist[#:style 'compact @item{@tt{win}: the native integer window handle} @item{@tt{evt-queue}: internal queue of pending event strings} @item{@tt{callback}: user event callback} @item{@tt{valid}: mutable flag indicating whether the wrapper considers the window active} @item{@tt{close-callback}: procedure invoked when the window is closed} ] Although the structure is transparent, user code should normally treat it as an opaque handle. } @;@defproc[(rkt-wv-win [wv rkt-wv?]) exact-integer?]{ @;Returns the native window handle associated with @racket[wv]. @;} @section{HTTP(S) Contexts} A context represents the shared HTTP(S) environment used by webviews. Contexts correspond to native WebEngine profiles and determine properties such as: @itemlist[#:style 'compact @item{cookies and cache} @item{injected JavaScript} @item{trusted certificates} ] @defproc[(rkt-webview-new-context [boilerplate-js string?] [server-cert bytes?]) exact-integer?]{ Creates a new HTTP(S) context and returns its native identifier. Arguments: @itemlist[#:style 'compact @item{@racket[boilerplate-js]: JavaScript source injected into pages} @item{@racket[server-cert]: optional certificate data} ] The returned context identifier can be passed to @racket[rkt-webview-create] to create webviews within that context. } @section{Creating Webviews} @defproc[(rkt-webview-create [context exact-integer?] [parent (or/c #f rkt-wv?)] [evt-callback (-> rkt-wv? string? any)] [close-callback (-> any)]) rkt-wv?]{ Creates a new webview window in the given HTTP(S) context. Arguments: @itemlist[#:style 'compact @item{@racket[context]: context identifier returned by @racket[rkt-webview-new-context]} @item{@racket[parent]: optional parent webview} @item{@racket[evt-callback]: procedure invoked for each event} @item{@racket[close-callback]: procedure invoked when the window is closed} ] The result is a new @racket[rkt-wv] structure. Events generated by the native layer are delivered asynchronously through @racket[evt-callback]. } @section{Window Lifecycle} @defproc[(rkt-webview-close [wv rkt-wv?]) boolean?]{ Requests that the webview window be closed. The wrapper forwards the request to the native backend and schedules cleanup of the event-processing loop. Returns @racket[#t]. } @defproc[(rkt-webview-valid? [wv rkt-wv?]) boolean?]{ Returns whether the webview still exists. The wrapper first checks its internal validity flag and then queries the native runtime. } @defproc[(rkt-webview-exit) void?]{ Closes all webviews and stops the background event-processing thread. This function is also registered with the Racket plumber so that cleanup occurs automatically when the process exits. } @section{Window Configuration} @defproc[(rkt-webview-set-title! [wv rkt-wv?] [title string?]) symbol?]{ Sets the native window title. Returns a result symbol such as: @itemlist[#:style 'compact @item{@racket['oke]} @item{@racket['failed]} ] } @defproc[(rkt-webview-set-ou-token [wv rkt-wv?] [token string?]) boolean?]{ Associates an Organizational Unit token with the window. This token may be used by the native layer when accepting certain self-signed certificates. } @section{Navigation} @defproc[(rkt-webview-set-url! [wv rkt-wv?] [url string?]) symbol?]{ Navigates the webview to the given URL. Returns a result symbol such as: @itemlist[#:style 'compact @item{@racket['oke]} @item{@racket['set_navigate_failed]} ] } @defproc[(rkt-webview-set-html! [wv rkt-wv?] [html string?]) symbol?]{ Loads HTML directly into the webview. Returns a result symbol such as: @itemlist[#:style 'compact @item{@racket['oke]} @item{@racket['set_html_failed]} ] } @section{JavaScript Execution} @defproc[(rkt-webview-run-js [wv rkt-wv?] [js string?]) symbol?]{ Executes JavaScript without returning a value. Returns a result symbol such as: @itemlist[#:style 'compact @item{@racket['oke]} @item{@racket['eval_js_failed]} ] } @defproc[(rkt-webview-call-js [wv rkt-wv?] [js string?]) (list/c symbol? string?)]{ Executes JavaScript and returns: @racketblock[ (list result value) ] where: @itemlist[#:style 'compact @item{@racket[result] is the native result code} @item{@racket[value] is a JSON string containing the returned data} ] The JSON structure is generated by the native backend. } @section{Window Geometry} @defproc[(rkt-webview-move [wv rkt-wv?] [x exact-integer?] [y exact-integer?]) symbol?]{ Moves the webview window to the given screen coordinates. } @defproc[(rkt-webview-resize [wv rkt-wv?] [width exact-integer?] [height exact-integer?]) symbol?]{ Resizes the window. } @defproc[(rkt-webview-show [wv rkt-wv?]) symbol?]{ Shows the window. } @defproc[(rkt-webview-hide [wv rkt-wv?]) symbol?]{ Hides the window. } @defproc[(rkt-webview-show-normal [wv rkt-wv?]) symbol?]{ Restores the window to its normal state. } @defproc[(rkt-webview-maximize [wv rkt-wv?]) symbol?]{ Maximizes the window. } @defproc[(rkt-webview-minimize [wv rkt-wv?]) symbol?]{ Minimizes the window. } @defproc[(rkt-webview-present [wv rkt-wv?]) symbol?]{ Requests that the window be presented to the user. } @defproc[(rkt-webview-window-state [wv rkt-wv?]) symbol?]{ Returns the current window state. Possible results: @itemlist[#:style 'compact @item{@racket['normal]} @item{@racket['minimized]} @item{@racket['maximized]} @item{@racket['hidden]} @item{@racket['normal_active]} @item{@racket['maximized_active]} ] } @section{Developer Tools} @defproc[(rkt-webview-open-devtools [wv rkt-wv?]) symbol?]{ Opens the browser developer tools window. } @section{Native Dialogs} Dialog functions return immediately with a status code. The user’s choice is delivered asynchronously through the event callback. @defproc[(rkt-webview-choose-dir [wv rkt-wv?] [title string?] [base-dir string?]) symbol?]{ Requests a directory-selection dialog. } @defproc[(rkt-webview-file-open [wv rkt-wv?] [title string?] [base-dir string?] [extensions string?]) symbol?]{ Requests a file-open dialog. } @defproc[(rkt-webview-file-save [wv rkt-wv?] [title string?] [base-dir string?] [extensions string?]) symbol?]{ Requests a file-save dialog. } @defproc[(rkt-webview-messagebox [wv rkt-wv?] [title string?] [message string?] [submessage string?] [type symbol?]) symbol?]{ Shows a native message box. } @section{Event Delivery} Each webview has an associated event callback. The callback has the form: @racketblock[ (λ (wv event-json) ...) ] Arguments: @itemlist[#:style 'compact @item{@racket[wv]: the webview handle} @item{@racket[event-json]: JSON event string from the native backend} ] Typical event types include: @itemlist[#:style 'compact @item{@tt{"show"}} @item{@tt{"hide"}} @item{@tt{"move"}} @item{@tt{"resize"}} @item{@tt{"closed"}} @item{@tt{"page-loaded"}} @item{@tt{"navigation-request"}} @item{@tt{"js-evt"}} ] The wrapper does not parse the JSON payload. @section{Version Information} @defproc[(rkt-webview-version) (list/c list? list?)]{ Returns version information for the native backend. Example result: @racketblock[ (list (list 'webview-c-api 1 0 0) (list 'qt 6 10 2)) ] } @section{Example} @racketblock[ (define ctx (rkt-webview-new-context "" #"")) (define wv (rkt-webview-create ctx #f (λ (wv evt) (displayln evt)) (λ () (displayln "closed")))) (rkt-webview-set-title! wv "Example") (rkt-webview-set-url! wv "https://example.org") ] @section{Summary} The FFI module provides a thin Racket interface to the native @tt{rktwebview_qt} backend. Key characteristics: @itemlist[#:style 'compact @item{thin wrapper around the native C API} @item{asynchronous event delivery} @item{JSON-based event payloads} @item{simple Racket structures for webviews} ]