Skip to content
M5DevsPublic

About

A lightweight, modern GTK3/GTK4 webview built on a pruned Blink/V8 core (685MB). GObject Introspection ready with zero compositor lock-in — a clean drop-in alternative to WebKitGTK for Linux.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

11 Commits

Folders and files

Repository files navigation

WebGTK

A lightweight GTK3/GTK4 webview built on the pruned Blink/V8 core, positioned as a drop-in replacement for WebKitGTK. The view renders the engine's offscreen (OSR) frames as an OpenGL texture inside a GtkGLArea, so it works on both GTK majors and needs no compositor.

Layout

include/webgtk/webgtk.h   Public C ABI (the only supported embedder interface)
include/webgtk/webgtk-view.h  GObject Introspection surface (bindings)
src/webgtk_backend_ops.h  Backend vtable: the seam between view and engine
src/webgtk_view.cc        GObject view wrapping GtkGLArea (GTK3 + GTK4)
src/backend_mock.cc       Deterministic software backend, no engine required
src/backend_chromium.cc   Real Blink/V8 backend (built when an engine is set)
examples/webgtk_demo.cc   Embedder demo, built for GTK3 and GTK4
examples/demo.py          PyGObject demo, drives the typelib only
tests/test_webgtk.c       C ABI and behaviour tests
scripts/                  Engine download/build tooling and GN stubs
scripts/package_release.sh  Release build plus CPack into dist/
cmake/webgtk.pc.in        pkg-config template for the webgtk-1.0/-4.0 modules
.github/workflows/        Engine build and publish pipeline
docs/ENGINE.md            Engine integration notes and current status

The view never talks to Blink, V8 or the mock directly. Everything engine specific goes through webgtk::BackendOps, so the same view binary works with the real engine and with the mock renderer.

Build

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug
cmake --build build
ctest --test-dir build --output-on-failure

Produces libwebgtk.so (GTK3), libwebgtk4.so (GTK4), gtk3-demo and gtk4-demo. A GtkGLArea needs a display, so the tests run under xvfb-run.

Debug builds enable ASan and UBSan. tests/lsan.supp suppresses the process-lifetime allocations the system GL/text stack makes, which LeakSanitizer would otherwise report.

Linking the engine

The Blink/V8 engine is optional. Without it everything builds and tests against the mock backend. To build the real backend, download the prebuilt engine and point CMake at it:

scripts/download_engine_prebuilt.sh
cmake -S . -B build -DWEBGTK_USE_PREBUILT_ENGINE=ON
cmake --build build

That adds src/backend_chromium.cc, defines WEBGTK_HAVE_ENGINE, and links libwebgtk_engine.so from engine/prebuilt/. It takes seconds: the engine is compiled once in CI (.github/workflows/build_engine.yml), never on your machine.

-DWEBGTK_ENGINE_DIR=<tree> points CMake at a Chromium source tree instead. A pruned source tree cannot be linked, so this mode does not add the backend to the library; it exposes the webgtk-verify-engine target, which compiles src/backend_chromium.cc with the exact flags gn emits and fails on any API drift:

scripts/setup_engine.sh
cmake -S . -B build -DWEBGTK_ENGINE_DIR=engine
cmake --build build --target webgtk-verify-engine

See docs/ENGINE.md for what the setup script restores and how the check works.

Using a view

#include <webgtk/webgtk.h>

webgtk_view *view = webgtk_view_new(800, 600);
webgtk_view_load_uri(view, "https://example.org/");
gtk_container_add(GTK_CONTAINER(window),
                  (GtkWidget *)webgtk_view_get_widget(view));

Mouse, scroll, key and text input are forwarded with the webgtk_view_mouse_* and webgtk_view_key_event / webgtk_view_text_input calls. Call webgtk_view_render to pull the current frame into a caller-owned RGBA8 buffer, which is also how the headless tests assert on output.

For richer input, build the event structs directly and hand them over; the GTK3 and GTK4 widgets use these internally, so they carry everything the engine needs:

webgtk_mouse_event press = {0};
press.type = WEBGTK_MOUSE_PRESS;
press.x = 120; press.y = 80;
press.button = WEBGTK_MOUSE_LEFT;
press.modifiers = WEBGTK_MOD_CONTROL;
press.click_count = 2;               /* double click */
webgtk_view_send_mouse_event(view, &press);

webgtk_key_event key = {0};
key.type = WEBGTK_KEY_PRESS;
key.keyval = 0x41;                   /* 'A' */
key.keycode = 38;                    /* hardware keycode */
key.text = "A";                      /* NULL when not printable */
webgtk_view_send_key_event(view, &key);

webgtk_scroll_event scroll = {0};
scroll.dy = -2.0;                    /* wheel down */
webgtk_view_send_scroll_event(view, &scroll);

webgtk_view_send_focus_event(view, 1);  /* view gained focus */

The widget wires its own toolkit's events to these entry points: GTK4 attaches GtkGestureClick, motion, scroll, key and focus controllers to the GtkGLArea; GTK3 connects the traditional button-press-event, motion-notify-event, scroll-event, key-press-event and focus signals and enables the matching event masks. Modifier state, click count, hardware keycode and committed text are all carried through, so a keystroke arrives as a real key press rather than a bare keysym.

webgtk_view_destroy releases everything the view owns. If the widget was added to a container it is detached first; the view holds its own reference to the widget, so it is safe to destroy the view before or after the window.

Talking to the page

The page and the embedder share a small bridge. From JavaScript:

window.webgtk.postMessage("clicked", JSON.stringify({x: 1, y: 2}));

The message is delivered to native code synchronously, with no polling loop. In C, register a handler for the name:

static void on_clicked(webgtk_view *view, const char *name,
                       const char *payload, void *user_data) {
  printf("%s: %s\n", name, payload);
}

webgtk_view_add_script_message_handler(view, "clicked", on_clicked, NULL);

In a GI binding, connect the signal instead; it fires for every message and needs no registration:

view.connect("script-message-received",
             lambda _v, name, payload: print(name, payload))

The bridge is injected at document start, so window.webgtk exists before the page's own scripts run. Payloads are strings; JSON-encode objects in the page and decode them natively.

Language bindings

The same libraries ship GObject Introspection data, so the view is usable from any GI-aware language with no C glue. A typelib is built per GTK major version (WebGTK-1.0 for GTK3, WebGTK-4.0 for GTK4), next to the shared library:

cmake -S . -B build -DENABLE_INTROSPECTION=ON   # default; needs g-ir-scanner
GI_TYPELIB_PATH=build LD_LIBRARY_PATH=build python3 examples/demo.py
import gi
gi.require_version("WebGTK", "1.0")
from gi.repository import Gtk, WebGTK

view = WebGTK.View.new_gtk3(800, 600)
view.load_html_string("<h1>Hello</h1>")
view.connect("javascript-result", lambda _v, json: print(json))
view.run_javascript("1 + 1")
window.add(view.widget)

WebGtkView is a real GObject that composes a GtkGLArea; the widget to add to a container is the read-only widget property. The flat C ABI in <webgtk/webgtk.h> stays dependency-free for embedders that only want the handful of functions.

Packaging and installation

cmake --install writes a conventional layout: headers to include/webgtk, the versioned libraries and typelibs to lib, the pkg-config files to lib/pkgconfig, and the .gir data to share/gir-1.0.

Consumers then find the library through pkg-config, one module per GTK major version:

cc app.c $(pkg-config --cflags --libs webgtk-1.0) -o app   # GTK3
cc app.c $(pkg-config --cflags --libs webgtk-4.0) -o app   # GTK4

The .pc prefix is resolved from the file's own location (${pcfiledir}), so the install tree is relocatable: an unpacked tarball works from any path.

scripts/package_release.sh builds in Release mode (no sanitizer runtimes, so the result links on a machine that does not have them) and runs CPack into dist/:

scripts/package_release.sh
#   webgtk-<ver>-linux-<arch>.tar.gz   portable install tree
#   libwebgtk0_<ver>-1_<arch>.deb      libwebgtk.so, libwebgtk4.so, typelibs
#   libwebgtk-dev_<ver>-1_<arch>.deb   headers, .pc files, .gir, linker symlinks
#   webgtk-demos_<ver>-1_<arch>.deb    gtk3-demo, gtk4-demo

The libraries carry a libwebgtk.so.0 soname, so the runtime and development packages can be upgraded independently. The .deb dependencies are declared explicitly, with t64 alternatives for the GLib/GTK packages renamed in Debian 13 and Ubuntu 24.04.

License

LGPL-2.1-or-later. See LICENSE.

About

A lightweight, modern GTK3/GTK4 webview built on a pruned Blink/V8 core (685MB). GObject Introspection ready with zero compositor lock-in — a clean drop-in alternative to WebKitGTK for Linux.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages