| Author | BlinkGTK Project |
| Target version | BlinkGTK 1.2.5-build3 (Chromium 156.0.8078.73) |
| Scope | A manual for developers who embed BlinkGTK in their applications. It includes what was learned running an EPUB reader built on BlinkGTK in production |
The meaning and behaviour of the API are defined by the public
header
blink_gtk/blink_gtk.h. Where this chapter and the header
disagree, follow the header.
BlinkGTK is a library for embedding Chromium's Blink rendering engine
into an
application as a GTK4 widget. It offers a C API
(BlinkWebView) with the same
design philosophy as WebKitGTK. A GTK4 application can display web
content,
navigate, talk to JavaScript, and serve custom URI schemes.
You do not need to build Chromium yourself — link
against the prebuilt
package (shared libraries + headers + pkg-config). This manual is
written from
the perspective of running an EPUB reader built on BlinkGTK in
production.
Top to bottom:
BlinkWebView inside
it as a childGtkWidget subclassYour application only touches layers 1 and 2; Chromium's process
model stays behind
the curtain. That said, the pitfalls in §1.5 (sandbox, profile, RUNPATH)
all
stem from this multi-process structure, so being aware it exists makes
trouble
much faster to diagnose.
#include <blink_gtk/blink_gtk.h>pkg-config --cflags --libs blinkgtk-0.1Include only
blink_gtk.h.On v1.2.0-build5 and earlier, the pre-GObject internal header
blink_web_view.h(2025)
was also bundled. Including it alongsideblink_gtk.hmakes the same typedef
name refer to two different types, which fails to compile every time:error: conflicting types for 'BlinkWebView'; have 'struct BlinkWebView'There is no reason to ship a header that cannot be used, so it has not been
bundled since then. If you need somethingblink_gtk.hdoes not offer,
please raise an issue.Your application should include
blink_gtk/blink_gtk.hand nothing else.The header is gone since v1.2.0-build6 (verified 2026-09-03 by unpacking
every devel package published at that time). The
headers shipped today areblink_gtk.h,blinkgtk_export.hand
blinkgtk_version.h.
Every public BlinkGTK API may be called only from the GTK
main thread
(see the Thread Safety Policy at the top of the header).
Callbacks — JavaScript results, cookies, printing — are likewise always
invoked
on the GTK main thread. From a worker thread, dispatch to the main
thread with
g_idle_add().
The process of an application that embeds BlinkGTK moves through these phases in one direction.
| Phase | Main API | Notes |
|---|---|---|
| (0) Pre-init configuration | blink_gtk_set_resources_path() and friends |
Must be called before
blink_gtk_init() |
| (1) Initialization | blink_gtk_init() |
Once, on the main thread, before any GTK/GLib call |
| (2) Creating the WebView | blink_web_view_new() /
_new_with_gpu_mode() |
Returns a GtkWidget. If you need a container, put it in your own
GtkOverlay / GtkBox |
| (3) Registration before load | blink_web_view_register_custom_scheme_full(),
register_message_handler(),
g_signal_connect(load-changed) |
Finish this before load_uri() |
| (4) Placement and display | gtk_window_set_child(),
gtk_window_present() |
Treat it as an ordinary GTK4 widget |
| (5) Load | blink_web_view_load_uri() |
Progress arrives on the load-changed signal |
| (6) Main loop | blink_gtk_run_main_loop() /
blink_gtk_quit_main_loop() |
Chromium's RunLoop, not GTK's |
| (7) Shutdown | blink_gtk_shutdown() |
After run_main_loop() returns (calling it
explicitly is recommended; see below) |
Whether and when to call
blink_gtk_shutdown()(BlinkGTK's recommendation)
How you call it Assessment After run_main_loop()returnsRecommended. Teardown order becomes deterministic (the complete example below takes this form) While the RunLoop is running, e.g. from a window close handler Safe. It does not deadlock; it switches to a deferred shutdown that runs once run_main_loop()returnsNot at all On Linux it works (the atexitsafety net catches it), but teardown is left to atexit and the order is non-deterministic. Not recommended. On Windows up to 1.2.5-build1, the process takes more than 20 seconds to exit and ends abnormallyOn Linux, skipping it still worked because an
atexithandler is registered once
ContentMainRunner::Runsucceeds, so teardown happens automatically while the
AtExitManager is alive. That is also why existing applications that never
called it kept working. On Windows this path ends abnormally during exit,
so call it explicitly on every OS.
The header states explicitly that these must be called
before
blink_gtk_init().
void blink_gtk_set_devtools_locale(const char* locale); /* NULL = follow the system locale */
const char* blink_gtk_get_devtools_locale(void);
void blink_gtk_set_icu_data_path(const char* path); /* absolute path to icudtl.dat */
void blink_gtk_set_resources_path(const char* path); /* root of the pak/snapshot files */You normally do not need any of them: the installed location
<prefix>/lib/chromium/ is detected automatically. Use
set_resources_path()
only for a non-standard layout, such as bundling the runtime inside your
own
application. If both are given, resources_path takes
precedence over
set_icu_data_path().
gboolean blink_gtk_init(int* argc, char*** argv);Passing argc/argv lets Chromium-style
command-line flags (--no-sandbox and
so on) be parsed here. FALSE means initialization failed.
Call it exactly once,
on the main thread, before any other GTK/GLib call.
GtkWidget* blink_web_view_new(void);
GtkWidget* blink_web_view_new_with_gpu_mode(BlinkGpuMode mode);
GtkWidget* blink_web_view_new_container(void);
BlinkGpuMode blink_web_view_get_gpu_mode(BlinkWebView* web_view);typedef enum {
BLINK_GPU_MODE_SOFTWARE = 0, /* CPU rendering only (default; no GPU needed) */
BLINK_GPU_MODE_SWIFTSHADER = 1, /* GL emulation via SwiftShader */
BLINK_GPU_MODE_EGL = 2, /* native EGL, hardware GPU */
} BlinkGpuMode;GtkWidget*. Use the
BLINK_WEB_VIEW() /BLINK_IS_WEB_VIEW() macros for type-safe access.blink_web_view_new()
and, if you need a container, put it in your ownGtkOverlay / GtkBox.
new_container() only wraps a WebView created with
blink_web_view_new()GtkOverlay. The GtkPicture inside is left
over from an earlier rendering method and drawsBlinkWebView* with
g_object_get_data(overlay, "blinkgtk-webview")).BLINK_GPU_MODE_SOFTWARE (the EPUB reader mentioned
above also uses it).BLINKGTK_GPU_MODE in Chapter 9 and Chapter 5
(rendering) for detail.Before calling load_uri(), put in place everything that
receives events
originating from the content.
/* Custom URI scheme (binary-capable variant; images and audio are returned without truncation) */
void blink_web_view_register_custom_scheme_full(
BlinkWebView* web_view, const char* scheme,
BlinkCustomSchemeBytesCallback callback, gpointer user_data);
/* JS -> C messages (window.blinkgtk.postMessage) */
void blink_web_view_register_message_handler(
BlinkWebView* web_view, const char* name,
BlinkMessageCallback callback, gpointer user_data);Load events arrive on the GObject signal
"load-changed".
typedef enum {
BLINK_LOAD_STARTED = 0,
BLINK_LOAD_COMMITTED = 1,
BLINK_LOAD_FINISHED = 2,
BLINK_LOAD_REDIRECTED = 3 /* reserved; the current runtime never emits it */
} BlinkLoadEvent;Note from production use:
BLINK_LOAD_REDIRECTED is reserved for the
future and is not emitted today. Also, starting with the v1.0.10 builds
distributed
from 2026-04-21 onward, the integer values of the enum were made
explicit to match
the values actually emitted. In earlier versions the
header values and the
emitted values differed, so ev == BLINK_LOAD_FINISHED
silently evaluated to
false. Do not mix headers from an old package with a new library.
BlinkWebView is an ordinary GTK4 widget: place it
with
gtk_window_set_child(), and call
blink_web_view_load_uri() after
gtk_window_present().
void blink_web_view_load_uri(BlinkWebView* web_view, const char* uri);
void blink_web_view_load_html(BlinkWebView* web_view, const char* html, const char* base_uri);int blink_gtk_run_main_loop(void); /* 0 = success. Blocks until it returns */
void blink_gtk_quit_main_loop(void);This runs Chromium's base::RunLoop, not
g_application_run(). The design
avoids a collision between the GTK and Chromium lifecycles and keeps
the
shutdown order under control. GLib's g_timeout_add() /
g_idle_add() still
fire under this loop (the EPUB reader uses GLib timeouts routinely for
recording and
diagnostics). Call blink_gtk_quit_main_loop() from wherever
you want to end
it, such as a window's close-request.
void blink_gtk_shutdown(void);Call it after run_main_loop() returns, just before the
process exits.
A compilable example, boiled down from the launch code of an EPUB
reader running
in production. It serves a local directory over the
custom scheme app:// and receives load completion through a
signal.
/* minimal-embedder.c — a minimal application embedding BlinkGTK (GTK4) */
#include <blink_gtk/blink_gtk.h>
#include <gtk/gtk.h>
#include <string.h>
/* Serve app://host/<path> from the current directory (binary is fine). NULL = 404 */
static GBytes* scheme_cb(BlinkWebView* wv, const char* uri,
char** out_mime, gpointer ud) {
(void)wv; (void)ud;
const char* p = strstr(uri, "://");
if (!p) return NULL;
const char* slash = strchr(p + 3, '/'); /* the '/' after the host */
const char* rel = slash ? slash + 1 : "index.html";
if (*rel == '\0') rel = "index.html";
if (strstr(rel, "..")) return NULL; /* path-traversal guard */
char* contents = NULL; gsize len = 0;
if (!g_file_get_contents(rel, &contents, &len, NULL)) return NULL;
if (out_mime && g_str_has_suffix(rel, ".html"))
*out_mime = g_strdup("text/html"); /* NULL = guess from the URI */
return g_bytes_new_take(contents, len);
}
static void on_load_changed(BlinkWebView* wv, BlinkLoadEvent ev, gpointer ud) {
(void)ud;
if (ev == BLINK_LOAD_FINISHED)
g_print("loaded: %s (title=%s)\n",
blink_web_view_get_uri(wv), blink_web_view_get_title(wv));
}
static gboolean on_close(GtkWindow* w, gpointer ud) {
(void)w; (void)ud;
blink_gtk_quit_main_loop();
return FALSE;
}
int main(int argc, char* argv[]) {
/* (0) For a non-standard layout, call blink_gtk_set_resources_path() here. */
/* (1) Initialize (before any GTK/GLib call) */
/* (1b) The rendering path is fixed at init. Choosing it later has no effect. */
blink_gtk_set_gpu_mode(BLINK_GPU_MODE_SOFTWARE);
if (!blink_gtk_init(&argc, &argv)) return 1;
/* (2) Window + WebView (software rendering recommended) */
GtkWidget* window = gtk_window_new();
gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768);
GtkWidget* webview = blink_web_view_new();
/* (3) Register before loading */
blink_web_view_register_custom_scheme_full(
BLINK_WEB_VIEW(webview), "app", scheme_cb, NULL);
g_signal_connect(webview, "load-changed", G_CALLBACK(on_load_changed), NULL);
g_signal_connect(window, "close-request", G_CALLBACK(on_close), NULL);
/* (4)-(5) Place, present, load */
gtk_window_set_child(GTK_WINDOW(window), webview);
gtk_window_present(GTK_WINDOW(window));
blink_web_view_load_uri(BLINK_WEB_VIEW(webview),
argc > 1 ? argv[1] : "app://local/index.html");
/* (6)-(7) Main loop, then shutdown */
int rc = blink_gtk_run_main_loop();
blink_gtk_shutdown();
return rc;
}Build and run:
export PKG_CONFIG_PATH="$BLINKGTK_PKG/lib/pkgconfig:$PKG_CONFIG_PATH"
cc -O2 -Wall -o minimal-embedder minimal-embedder.c \
-Wl,-rpath,'$ORIGIN' \
$(pkg-config --cflags --libs blinkgtk-0.1)
# The surest arrangement is to run it from the same directory as the runtime
# (the .so files and the Chromium resources)
cp minimal-embedder "$BLINKGTK_PKG/lib/chromium/"
"$BLINKGTK_PKG/lib/chromium/minimal-embedder" --no-sandbox --no-zygoteThe next section explains why -Wl,-rpath,'$ORIGIN' and
"place it in
lib/chromium and launch from there".
So that you can get BlinkGTK running on the first try, here are the
problems
that actually occurred while running the EPUB reader.
libblinkgtk.so must be paired strictly with the Chromium
resources of the same
package (v8_context_snapshot.bin and so on). Do not build a
binary with only
the absolute rpath that comes from the .pc file and then
copy it into the
lib/chromium/ of a different package and
run it: it will load the .so
from the package it was built against, and the renderer exits
abnormally at once on a V8
snapshot mismatch (confirmed in practice on 2026-07-07). Either
of these avoids
it:
-Wl,-rpath,'$ORIGIN'
in first, then place the binary inlib/chromium/ of the package you want to use and launch
it there. The.so files next to it always win, which makes the binary
position-independentLD_LIBRARY_PATH=<pkg>/lib/chromium
explicitly at launch.Sample binaries as distributed may have a build-time path baked into
RUNPATH, so
if you intend to move or swap packages, building your own is the safe
route.
~/.blink_gtk/ is shared by every instanceIn measurements, passing --user-data-dir does not
separate profiles;
~/.blink_gtk/ is shared by every BlinkGTK process belonging
to the same user.
Furthermore, persistence to disk (localStorage and the like) happens
only on a
clean exit path such as SIGTERM — with SIGKILL or _exit()
it is lost. If a test
dirties the state, the practical remedy is to delete the relevant data
under
~/.blink_gtk/ by hand. If running several applications at
once or separating
profiles is a requirement, design around this behaviour.
--no-sandbox --no-zygoteWhere the Chromium sandbox cannot be set up — containers, restricted
user
namespaces, some development environments — the child processes will not
start
unless the argv you hand to blink_gtk_init() includes
--no-sandbox --no-zygote. The EPUB reader's launch script
always passes both. For the
equivalent through the environment, see BLINKGTK_NO_SANDBOX
in Chapter 9. Use
it understanding the security implication (the sandbox is switched
off).
The EGL (hardware GPU) path is experimental; having evaluated it, the
EPUB reader
runs on software for the time being (both rendering
vertical-writing EPUB body
text and taking screenshots are stable on software). And, as noted
above, the
mode is per process, so it cannot be switched per WebView.
The blink_web_view_capture_screenshot() family saves a
frame re-rendered on the
renderer side, so a fault in the compositor/display stage — the display
going
blank while the content is fine — may not appear in it. Do not accept
a
display-side verification on the strength of a captured PNG alone. A
defect was once missed this
way; the EPUB reader now also runs a check that reads the GTK
composition result
directly.
const char* blink_gtk_get_version(void); /* e.g. "1.2.1" */
const char* blink_gtk_get_chromium_version(void); /* e.g. "NNN.0.NNNN.N" */
/* Build number (since 1.2.2). The build number counts how many times the same
* version was rebuilt. Behaviour can differ between builds of one version. */
const char* blink_gtk_get_build(void); /* e.g. "2". NULL if unknown */
const char* blink_gtk_get_version_full(void); /* e.g. "X.Y.Z-buildN". Never NULL */These come from the .so linked at run time, not from
compile-time macros
(BLINKGTK_VERSION / CHROMIUM_VERSION), so what
you display follows the
library without rebuilding your application. We recommend always
recording these
run-time values in logs and bug reports.
load_uri, history, the detail of load-changed
and load-failedregister_custom_scheme_full(), serving binary data, the
relationship with fetch()capture_screenshot familyBLINKGTK_GPU_MODE, with
practical examples(Chapter titles are planned and may change.)
The first draft was written from the sources below. It was then
re-verified
against the then-current distribution on 2026-09-03 and again on
2026-09-08,
bringing the statements about bundled headers and the version examples
in line
with the artifacts.
include/blinkgtk-0.1/blink_gtk/blink_gtk.h. Functiondocs/04-user-guides/api-reference/c-api-reference-en.md