-
This commit is contained in:
348
scrbl/rktwebview-api.scrbl
Normal file
348
scrbl/rktwebview-api.scrbl
Normal file
@@ -0,0 +1,348 @@
|
||||
#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": <bool>, "result": <value-or-false>, "exn": <string-or-false>}}.}
|
||||
]
|
||||
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": <bool>}}
|
||||
@item{@tt{"event":"show"}}
|
||||
@item{@tt{"event":"hide"}}
|
||||
@item{@tt{"event":"move","x": <int>,"y": <int>}}
|
||||
@item{@tt{"event":"resize","w": <int>,"h": <int>}}
|
||||
@item{@tt{"event":"can-close?"}}
|
||||
@item{@tt{"event":"closed"}}
|
||||
@item{@tt{"event":"js-evt","js-evt": <object>}}
|
||||
]
|
||||
|
||||
|
||||
@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.
|
||||
``
|
||||
Reference in New Issue
Block a user