BlinkGTK Python (PyGObject) Tutorial

Target version: BlinkGTK 1.2.5 or later
Language: 日本語 | English


Table of Contents

  1. Introduction
  2. PyGObject Setup
  3. Minimal Browser
  4. Signal Handling
  5. Property Monitoring
  6. Navigation Features
  7. Practical Usage
  8. Troubleshooting

Introduction

BlinkGTK supports GObject Introspection, so you can use it from Python through PyGObject.
Every complete program in this tutorial was run and checked with BlinkGTK 1.2.5 (Chromium 156).

Requirements

If you used code from an earlier version of this tutorial

The code that used to be on this page does not work as written. Change these three points.

Earlier code 1.2.5 and later
Creating widgets without initializing Call BlinkGTK.gtk_init(sys.argv[:1]) before creating any widget
app.run(sys.argv) BlinkGTK.gtk_application_run(app) followed by BlinkGTK.gtk_shutdown()
Other namespaces such as BlinkGtk, or import blink_gtk gi.require_version("BlinkGTK", "0.1") and from gi.repository import BlinkGTK

With app.run() the window appears, but Chromium's work never runs and pages never load.


PyGObject Setup

Installing the Required Packages

# Fedora
sudo dnf install python3-gobject gtk4 blinkgtk-bin blinkgtk-bin-gir

# Debian / Ubuntu
sudo apt install python3-gi gir1.2-gtk-4.0
# Install BlinkGTK and its introspection data from the downloaded DEB files (there is no APT repository)
sudo apt install ./libblinkgtk-0.1-0_<version>-1_amd64.deb ./gir1.2-blinkgtk-0.1_<version>-1_amd64.deb

For the repository setup needed to install the BlinkGTK packages, see the installation guide.

Verifying the Import

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")
from gi.repository import BlinkGTK, Gtk

print(f"GTK {Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}")
print(f"BlinkGTK {BlinkGTK.gtk_get_version()} (Chromium {BlinkGTK.gtk_get_chromium_version()})")

BlinkGTK.gtk_get_version() and BlinkGTK.gtk_get_chromium_version() can be called before initialization.
If this prints 1.2.4 or earlier, update BlinkGTK.


Minimal Browser

Basic Usage

Save the following program as minimal.py and run it with blinkgtk-run python3 minimal.py.

Always run Python programs through blinkgtk-run. BlinkGTK (Chromium) needs its memory allocator to be loaded from the very start of the process. C applications load it at startup, but in Python BlinkGTK is loaded later, so without blinkgtk-run the program aborts when it opens an HTTPS page. blinkgtk-run is at /usr/bin/blinkgtk-run in the RPM and DEB packages, and at usr/bin/blinkgtk-run where you extracted the tarball. It uses the LD_PRELOAD environment variable, so programs started from your program inherit it.
Pass a URL as an argument to open that page.

#!/usr/bin/env python3
"""A minimal BlinkGTK browser"""
import sys

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")
from gi.repository import BlinkGTK, Gtk  # noqa: E402


def on_activate(app, url):
    window = Gtk.ApplicationWindow(application=app, title="BlinkGTK Minimal Browser")
    window.set_default_size(1024, 768)

    view = BlinkGTK.WebView.new()
    window.set_child(view)
    window.present()

    view.load_uri(url)


def main():
    url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/"

    # 1. Initialize BlinkGTK (once, before creating any GTK widget)
    ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])
    if not ok:
        print("Could not initialize BlinkGTK", file=sys.stderr)
        return 1

    # 2. Create the application and run it with the BlinkGTK main loop
    app = Gtk.Application(application_id="org.example.MinimalBrowser")
    app.connect("activate", on_activate, url)
    rc = BlinkGTK.gtk_application_run(app)  # returns when the last window is closed

    # 3. Shut BlinkGTK down
    BlinkGTK.gtk_shutdown()
    return rc


if __name__ == "__main__":
    sys.exit(main())

Code Explanation

1. Version Specification

gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")

State the library versions explicitly. Put these lines before from gi.repository import ....

2. Initialization

ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])

3. Creating the WebView

view = BlinkGTK.WebView.new()

4. Main Loop and Shutdown

rc = BlinkGTK.gtk_application_run(app)
BlinkGTK.gtk_shutdown()

Signal Handling

Basic Signals

BlinkGTK.WebView emits the following four load-related signals.

Signal Handler arguments Description
load-changed (view, event: BlinkGTK.LoadEvent) The load state changed
load-failed (view, error_code: int, failing_uri: str) A load ended without committing (for example, aborted)
title-changed (view) The page title changed. Read the value with view.get_title()
uri-changed (view) The URI changed. Read the value with view.get_uri()

title-changed and uri-changed do not pass the new value as an argument.

Connecting Signals

def on_load_changed(view, event):
    if event == BlinkGTK.LoadEvent.FINISHED:
        print(f"Load finished: {view.get_title()} ({view.get_uri()})")


def on_load_failed(view, error_code, failing_uri):
    if failing_uri != (view.get_uri() or failing_uri):
        return  # A failure that is not for the page on screen, such as an iframe
    if error_code == -3:
        return  # Aborted. Happens normally with the stop button or a new load
    print(f"Load did not commit: {failing_uri} (error code {error_code})")


def on_title_changed(view):
    window.set_title(f"{view.get_title()} - Browser")


view.connect("load-changed", on_load_changed)
view.connect("load-failed", on_load_failed)
view.connect("title-changed", on_title_changed)

BlinkGTK.LoadEvent Values

Value Meaning
BlinkGTK.LoadEvent.STARTED A load started with load_uri() / load_html(). Not emitted for links, back, forward or reload
BlinkGTK.LoadEvent.COMMITTED The document to display was committed
BlinkGTK.LoadEvent.FINISHED The load finished
BlinkGTK.LoadEvent.REDIRECTED Reserved for future use. Not emitted at present

Compare against the names above, not against numbers.
Because STARTED does not arrive for every navigation, use notify::is-loading (see Property Monitoring) to tell whether a load is in progress.

When load-failed Is and Is Not Emitted


Property Monitoring

notify::property-name Signals

Changes to GObject properties arrive through notify::property-name signals.
The handler receives (view, pspec). Read the value as, for example, view.props.title.

def on_title_notify(view, pspec):
    print(f"Title: {view.props.title}")


def on_uri_notify(view, pspec):
    print(f"URI: {view.props.uri}")


view.connect("notify::title", on_title_notify)
view.connect("notify::uri", on_uri_notify)

Available Properties

Property Type Description
uri str The current URI
title str The page title
zoom-level float The zoom factor; 1.0 is 100% (read/write)
is-loading bool Whether a load is in progress

Whether a Load Is in Progress (is-loading)

Switch loading indicators (a progress bar, the stop and reload buttons) with notify::is-loading.
It arrives when a load starts and when it ends, for every kind of navigation, including links, back, forward and reload. Read the value with view.is_loading().
It may arrive twice in a row at the start, so decide by the value, not by the number of notifications.

def on_is_loading_changed(view, pspec):
    loading = view.is_loading()
    stop_button.set_sensitive(loading)
    reload_button.set_sensitive(not loading)


view.connect("notify::is-loading", on_is_loading_changed)

The load progress (0.0 to 1.0) is available from view.get_estimated_load_progress().
It is not a property, so there is no change notification. See the example under Practical Usage.


Method Description
load_uri(uri) Open a URI
reload() Reload
stop() Stop loading
go_back() / go_forward() Go back / forward in history
can_go_back() / can_go_forward() Whether going back / forward is possible

A Browser with Back and Forward Buttons and a URL Entry

#!/usr/bin/env python3
"""A browser with back, forward, reload and a URL entry"""
import sys

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")
from gi.repository import BlinkGTK, Gtk  # noqa: E402


class BrowserWithNavigation(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_default_size(1024, 768)

        toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)

        self.back_button = Gtk.Button(label="←", sensitive=False)
        self.back_button.connect("clicked", lambda _b: self.webview.go_back())
        toolbar.append(self.back_button)

        self.forward_button = Gtk.Button(label="→", sensitive=False)
        self.forward_button.connect("clicked", lambda _b: self.webview.go_forward())
        toolbar.append(self.forward_button)

        reload_button = Gtk.Button(label="↻")
        reload_button.connect("clicked", lambda _b: self.webview.reload())
        toolbar.append(reload_button)

        self.url_entry = Gtk.Entry(hexpand=True)
        self.url_entry.connect("activate", self.on_url_activate)
        toolbar.append(self.url_entry)

        self.webview = BlinkGTK.WebView.new()
        self.webview.set_vexpand(True)
        # Update the buttons when a load starts or ends (arrives for every kind of navigation)
        self.webview.connect("notify::is-loading", self.on_is_loading_changed)
        self.webview.connect("title-changed", self.on_title_changed)
        self.webview.connect("uri-changed", self.on_uri_changed)

        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        main_box.append(toolbar)
        main_box.append(self.webview)
        self.set_child(main_box)

    def on_url_activate(self, entry):
        url = entry.get_text().strip()
        if url and "://" not in url and not url.startswith(("about:", "data:")):
            url = "https://" + url
        if url:
            self.webview.load_uri(url)

    def on_is_loading_changed(self, webview, pspec):
        self.update_navigation_buttons()

    def on_title_changed(self, webview):
        title = webview.get_title()
        self.set_title(f"{title} - Browser" if title else "Browser")

    def on_uri_changed(self, webview):
        self.url_entry.set_text(webview.get_uri() or "")
        self.update_navigation_buttons()

    def update_navigation_buttons(self):
        self.back_button.set_sensitive(self.webview.can_go_back())
        self.forward_button.set_sensitive(self.webview.can_go_forward())


def on_activate(app, url):
    window = BrowserWithNavigation(application=app)
    window.present()
    window.webview.load_uri(url)


def main():
    url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/"
    ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])
    if not ok:
        print("Could not initialize BlinkGTK", file=sys.stderr)
        return 1
    app = Gtk.Application(application_id="org.example.NavigationBrowser")
    app.connect("activate", on_activate, url)
    rc = BlinkGTK.gtk_application_run(app)
    BlinkGTK.gtk_shutdown()
    return rc


if __name__ == "__main__":
    sys.exit(main())

The back and forward buttons are updated on uri-changed and notify::is-loading, so they are also updated after navigation through links, back and forward.


Practical Usage

A Header Bar and a Progress Bar

#!/usr/bin/env python3
"""A browser with a header bar and a load progress bar"""
import sys

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")
from gi.repository import BlinkGTK, GLib, Gtk  # noqa: E402


class ProgressBrowser(Gtk.ApplicationWindow):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.set_default_size(1200, 900)
        self.set_titlebar(Gtk.HeaderBar())
        self.set_title("Browser")

        self.progress_bar = Gtk.ProgressBar(visible=False)
        self.timer_id = 0

        self.webview = BlinkGTK.WebView.new()
        self.webview.set_vexpand(True)
        # Receive the start and end of every load with notify::is-loading (also for links, back and reload)
        self.webview.connect("notify::is-loading", self.on_is_loading_changed)
        self.webview.connect("notify::title", self.on_title_notify)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.append(self.progress_bar)
        vbox.append(self.webview)
        self.set_child(vbox)

    def on_is_loading_changed(self, webview, pspec):
        if webview.is_loading():
            self.progress_bar.set_visible(True)
            if self.timer_id == 0:
                # The progress value has no change notification, so read it every 0.1 s while loading
                self.progress_bar.set_fraction(0.0)
                self.timer_id = GLib.timeout_add(100, self.update_progress)
        else:
            self.progress_bar.set_visible(False)
            if self.timer_id:
                GLib.source_remove(self.timer_id)
                self.timer_id = 0
            print(f"Load ended: {webview.get_title() or '(no title)'}", flush=True)

    def update_progress(self):
        self.progress_bar.set_fraction(self.webview.get_estimated_load_progress())
        return GLib.SOURCE_CONTINUE

    def on_title_notify(self, webview, pspec):
        self.set_title(webview.props.title or "Browser")


def on_activate(app, url):
    window = ProgressBrowser(application=app)
    window.present()
    window.webview.load_uri(url)


def main():
    url = sys.argv[1] if len(sys.argv) > 1 else "https://example.com/"
    ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])
    if not ok:
        print("Could not initialize BlinkGTK", file=sys.stderr)
        return 1
    app = Gtk.Application(application_id="org.example.ProgressBrowser")
    app.connect("activate", on_activate, url)
    rc = BlinkGTK.gtk_application_run(app)
    BlinkGTK.gtk_shutdown()
    return rc


if __name__ == "__main__":
    sys.exit(main())

About Running JavaScript

There is currently no method to run JavaScript in the page from Python and get the result back (the equivalent of WebKitGTK's evaluate_javascript()).
Exchanging messages between the page and the application is described in the App Integration API (a C reference).


Troubleshooting

ModuleNotFoundError: No module named 'gi'

PyGObject is not installed.

# Fedora
sudo dnf install python3-gobject

# Debian / Ubuntu
sudo apt install python3-gi

ValueError: Namespace BlinkGTK not available

The BlinkGTK introspection data (BlinkGTK-0.1.typelib) cannot be found.
Install blinkgtk-bin-gir on Fedora or gir1.2-blinkgtk-0.1 on Debian / Ubuntu.

If it is still not found, see the troubleshooting section of the installation guide.

The Window Appears but the Page Does Not

Check that you are not running the application with app.run(). It must be run with BlinkGTK.gtk_application_run(app).
Also check that BlinkGTK.gtk_init() is called before any widget is created.

The Program Exits Right After Starting

With BlinkGTK 1.2.4 and earlier, starting from Python fails because Chromium's child processes cannot start.
Check the version with BlinkGTK.gtk_get_version() and update to 1.2.5 or later.

AttributeError: 'WebView' object has no attribute ...

Check that you are not using names from earlier material. The following names do not exist.

Name that does not exist Use instead
execute_javascript() Not available (About Running JavaScript)
set_enable_javascript() set_javascript_enabled()
set_auto_load_images() set_images_enabled()
set_enable_local_storage() set_local_storage_enabled()
estimated-load-progress property get_estimated_load_progress() method

Sample Code

Complete programs that were run and checked are in examples.

blinkgtk-run python3 minimal_browser.py https://example.com/

Summary

The key points for using BlinkGTK from Python:

Next steps:


BlinkGTK Project | Copyright 2025 | BSD-3-Clause License