Files
racket-webview/scrbl/racket-webview-qt.scrbl

493 lines
12 KiB
Racket
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 include:
@itemlist[#:style 'compact
@item{@racket['normal]}
@item{@racket['minimized]}
@item{@racket['maximized]}
@item{@racket['hidden]}
]
}
@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 users 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}
]