BlinkGTK Python (PyGObject) Tutorial

Python Language Bindings


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, making it accessible from Python through PyGObject.

BlinkGTK Features

Prerequisites


PyGObject Setup

Installing Required Packages

# Fedora/RHEL
sudo dnf install python3-gobject gtk4 gobject-introspection-devel

# Ubuntu/Debian
sudo apt install python3-gi python3-gi-cairo gir1.2-gtk-4.0

# Arch Linux
sudo pacman -S python-gobject gtk4

Installing BlinkGTK

Using Development Build (Local)

# Build BlinkGTK (requires C++/Chromium build)
cd $BLINKGTK_ROOT
# See BUILD.md for build instructions

# Generate GIR/typelib files
./scripts/generate_gir.sh

# Set GI_TYPELIB_PATH when using from Python
export GI_TYPELIB_PATH=$BLINKGTK_ROOT/gir:$GI_TYPELIB_PATH

System-wide Installation (Future)

# In the future, it will be auto-detected via pkg-config
sudo cp gir/BlinkGTK-0.1.typelib /usr/lib64/girepository-1.0/

Verifying Import

import gi
gi.require_version('Gtk', '4.0')
gi.require_version('BlinkGTK', '0.1')

from gi.repository import Gtk, BlinkGTK

print(f"GTK version: {Gtk.get_major_version()}.{Gtk.get_minor_version()}")
print(f"BlinkGTK loaded successfully!")

Minimal Browser

Basic Usage

#!/usr/bin/env python3
import gi
import sys
import os

# For local build typelib
typelib_dir = os.path.join(os.path.dirname(__file__), '../../gir')
if os.path.exists(typelib_dir):
    gi.require_foreign('cairo')
    from gi.repository import GLib
    GLib.setenv('GI_TYPELIB_PATH', typelib_dir, True)

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

from gi.repository import Gtk, BlinkGTK

class MinimalBrowser(Gtk.ApplicationWindow):
    """Minimal BlinkGTK browser"""

    def __init__(self, app):
        super().__init__(application=app, title="BlinkGTK Minimal Browser")
        self.set_default_size(1024, 768)

        # Create BlinkWebView
        self.webview = BlinkGTK.WebView.new()
        self.set_child(self.webview)

        # Load URL
        self.webview.load_uri("https://www.example.com")

def on_activate(app):
    window = MinimalBrowser(app)
    window.present()

def main():
    app = Gtk.Application(application_id='org.example.MinimalBrowser')
    app.connect('activate', on_activate)
    return app.run(sys.argv)

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

Code Explanation

1. Setting GI_TYPELIB_PATH

typelib_dir = os.path.join(os.path.dirname(__file__), '../../gir')
if os.path.exists(typelib_dir):
    gi.require_foreign('cairo')
    from gi.repository import GLib
    GLib.setenv('GI_TYPELIB_PATH', typelib_dir, True)

2. Version Specification

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

3. Creating BlinkWebView

self.webview = BlinkGTK.WebView.new()

Signal Handling

Basic Signals

BlinkWebView emits the following signals:

Signal Name Arguments Description
load-changed BlinkLoadEvent Page load state changes
load-failed BlinkLoadEvent, uri, error Load failure
title-changed None Page title changes

Connecting Signals

class BrowserWindow(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app)

        self.webview = BlinkGTK.WebView.new()
        self.set_child(self.webview)

        # Connect signals
        self.webview.connect("load-changed", self.on_load_changed)
        self.webview.connect("load-failed", self.on_load_failed)
        self.webview.connect("title-changed", self.on_title_changed)

        self.webview.load_uri("https://www.example.com")

    def on_load_changed(self, webview, load_event):
        """Load state change handler"""
        # BlinkLoadEvent: 0=STARTED, 1=REDIRECTED, 2=COMMITTED, 3=FINISHED
        event_names = ["STARTED", "REDIRECTED", "COMMITTED", "FINISHED"]
        print(f"Load state: {event_names[load_event]}")

        if load_event == 3:  # FINISHED
            uri = self.webview.get_uri()
            title = self.webview.get_title()
            print(f"Page loaded: {title} ({uri})")

    def on_load_failed(self, webview, load_event, failing_uri, error):
        """Load failure handler"""
        print(f"Load failed: {failing_uri}")
        print(f"Error: {error.message if error else 'Unknown'}")
        return True  # Indicate signal has been handled

    def on_title_changed(self, webview):
        """Title change handler"""
        title = self.webview.get_title()
        self.set_title(f"{title} - Browser")

BlinkLoadEvent Details

# BlinkLoadEvent enumeration
BLINK_LOAD_STARTED    = 0  # Load started
BLINK_LOAD_REDIRECTED = 1  # Redirect
BLINK_LOAD_COMMITTED  = 2  # Navigation committed
BLINK_LOAD_FINISHED   = 3  # Load finished

def on_load_changed(self, webview, load_event):
    if load_event == 0:
        print("Loading started")
    elif load_event == 1:
        print("Redirecting")
    elif load_event == 2:
        print("Navigation committed")
    elif load_event == 3:
        print("Loading finished")

Property Monitoring

notify::property-name Signals

GObject property changes can be monitored via notify::property-name signals.

class ProgressMonitor(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app)

        self.webview = BlinkGTK.WebView.new()
        self.set_child(self.webview)

        # Connect property change notifications
        self.webview.connect("notify::estimated-load-progress",
                           self.on_progress_changed)
        self.webview.connect("notify::uri", self.on_uri_changed)
        self.webview.connect("notify::title", self.on_title_changed)

    def on_progress_changed(self, webview, pspec):
        """Load progress change"""
        progress = self.webview.get_estimated_load_progress()
        print(f"Progress: {progress:.0%}")

    def on_uri_changed(self, webview, pspec):
        """URI change"""
        uri = self.webview.get_uri()
        print(f"URI changed: {uri}")

    def on_title_changed(self, webview, pspec):
        """Title change (via property)"""
        title = self.webview.get_title()
        print(f"Title: {title}")

Available Properties

Property Name Type Description
uri str Current URI
title str Page title
estimated-load-progress float Load progress (0.0~1.0)
is-loading bool Whether loading

class BrowserWithNavigation(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app)

        # Toolbar
        toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)

        # Back button
        back_button = Gtk.Button(label="◀")
        back_button.connect("clicked", lambda btn: self.webview.go_back())
        toolbar.append(back_button)

        # Forward button
        forward_button = Gtk.Button(label="▶")
        forward_button.connect("clicked", lambda btn: self.webview.go_forward())
        toolbar.append(forward_button)

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

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

        # Main box
        main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        main_box.append(toolbar)

        self.webview = BlinkGTK.WebView.new()
        main_box.append(self.webview)

        self.set_child(main_box)

    def on_url_activate(self, entry):
        """URL Enter key handler"""
        url = entry.get_text()
        self.webview.load_uri(url)

Checking Navigation State

# Update back/forward button enabled/disabled state
def update_navigation_buttons(self):
    can_go_back = self.webview.can_go_back()
    can_go_forward = self.webview.can_go_forward()

    self.back_button.set_sensitive(can_go_back)
    self.forward_button.set_sensitive(can_go_forward)

Practical Usage

UI Component Integration

class ModernBrowser(Gtk.ApplicationWindow):
    def __init__(self, app):
        super().__init__(application=app, title="Modern Browser")
        self.set_default_size(1200, 900)

        # Header bar
        header = Gtk.HeaderBar()
        self.set_titlebar(header)

        # Progress bar
        self.progress_bar = Gtk.ProgressBar()

        # WebView
        self.webview = BlinkGTK.WebView.new()
        self.webview.connect("notify::estimated-load-progress",
                           self.on_progress_changed)

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

    def on_progress_changed(self, webview, pspec):
        progress = self.webview.get_estimated_load_progress()
        self.progress_bar.set_fraction(progress)
        self.progress_bar.set_visible(progress < 1.0)

JavaScript Execution

# Run JavaScript and receive the result as a JSON string

def on_result(result, user_data):
    # result is a JSON-encoded string, or None on error
    print("Result:", result)

self.webview.execute_javascript("document.title", on_result, None)

Pass None as the callback if you do not need the result.

self.webview.execute_javascript("document.body.style.zoom = '1.5'", None, None)

Custom Protocol Handlers

# Custom protocol handlers (future feature)
# Example: handling myapp:// scheme

Troubleshooting

Issue: ModuleNotFoundError: No module named 'gi'

Cause: PyGObject is not installed

Solution:

# Fedora
sudo dnf install python3-gobject

# Ubuntu
sudo apt install python3-gi

Issue: ValueError: Namespace BlinkGTK not available

Cause: BlinkGTK typelib not found

Solution:

# Set GI_TYPELIB_PATH
import os
from gi.repository import GLib

typelib_dir = "/path/to/BlinkGTK/gir"
GLib.setenv('GI_TYPELIB_PATH', typelib_dir, True)

Or install system-wide:

sudo cp gir/BlinkGTK-0.1.typelib /usr/lib64/girepository-1.0/

Issue: AttributeError: 'WebView' object has no attribute 'load_uri'

Cause: Old BlinkGTK version or GIR not generated correctly

Solution:

# Regenerate GIR
cd $BLINKGTK_ROOT
./scripts/generate_gir.sh

# Clear Python cache
rm -rf __pycache__

Enabling Debug Logs

import logging

# GObject debug logs
logging.basicConfig(level=logging.DEBUG)

# GI_TYPELIB debug via environment variable
import os
os.environ['G_MESSAGES_DEBUG'] = 'all'

Sample Code

Complete sample code is available at:

How to run (C version):

cd $BLINKGTK_ROOT
gcc -o blinkgtk_browser examples/blinkgtk_browser.c $(pkg-config --cflags --libs blinkgtk-0.1)
./blinkgtk_browser

Summary

Using BlinkGTK from Python offers the following benefits:

Next Steps:


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