最終更新: 2026-10-21
対象バージョン: BlinkGTK 1.2.5 以降
レベル: 初級〜中級
言語: 日本語 | English
このチュートリアルでは、Python で BlinkGTK
のシグナルを使ったイベント処理を学びます。
完全なプログラムは、どれも BlinkGTK 1.2.5 (Chromium 156)
で実行して確かめています。
blinkgtk-run python3 <ファイル> の形で (理由)以前このページに載っていたコードは、import blink_gtk・app.run()・引数付きの
title-changed など、
実際の API
と異なる書き方をしていたため動きませんでした。このページのコードに置き換えてください。
次のプログラムは、読み込みに関わる 4
つのシグナルをコンソールに出します。
blinkgtk-run python3 signals.py https://example.com/
のように実行します (blinkgtk-run
が要る理由)。
#!/usr/bin/env python3
"""読み込みに関わる 4 つのシグナルをコンソールに出す"""
import sys
import gi
gi.require_version("Gtk", "4.0")
gi.require_version("BlinkGTK", "0.1")
from gi.repository import BlinkGTK, Gtk # noqa: E402
EVENT_NAMES = {
BlinkGTK.LoadEvent.STARTED: "開始",
BlinkGTK.LoadEvent.COMMITTED: "コミット",
BlinkGTK.LoadEvent.FINISHED: "完了",
}
def on_load_changed(view, event):
print(f"[load-changed] {EVENT_NAMES.get(event, event)}: {view.get_uri()}", flush=True)
def on_load_failed(view, error_code, failing_uri):
print(f"[load-failed] エラー番号 {error_code}: {failing_uri}", flush=True)
def on_title_changed(view):
print(f"[title-changed] {view.get_title()}", flush=True)
def on_uri_changed(view):
print(f"[uri-changed] {view.get_uri()}", flush=True)
def on_activate(app, url):
window = Gtk.ApplicationWindow(application=app, title="Signal Example")
window.set_default_size(800, 600)
view = BlinkGTK.WebView.new()
view.connect("load-changed", on_load_changed)
view.connect("load-failed", on_load_failed)
view.connect("title-changed", on_title_changed)
view.connect("uri-changed", on_uri_changed)
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/"
ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])
if not ok:
print("BlinkGTK を初期化できませんでした", file=sys.stderr)
return 1
app = Gtk.Application(application_id="org.example.SignalExample")
app.connect("activate", on_activate, url)
rc = BlinkGTK.gtk_application_run(app)
BlinkGTK.gtk_shutdown()
return rc
if __name__ == "__main__":
sys.exit(main())https://example.com/
を開くと、次の順にシグナルが届きます。
[uri-changed] https://example.com/
[load-changed] 開始: https://example.com/
[load-changed] コミット: https://example.com/
[title-changed] Example Domain
[load-changed] 完了: https://example.com/
ページによっては、文書が確定したときにも uri-changed
がもう一度届きます (ローカルのファイルを開いた場合など)。
uri-changed
が何回届くかに依存しない書き方にしてください。
どのプログラムも、次の順序で BlinkGTK を使います。
BlinkGTK.gtk_init(sys.argv[:1]) —
ウィジェットを作る前に 1 回だけBlinkGTK.gtk_application_run(app) —
app.run() は使わない。最後のウィンドウを閉じると戻るBlinkGTK.gtk_shutdown() — 最後に呼ぶ# 簡単なログ出力 (value_nick は "started" などの名前)
web_view.connect("load-changed",
lambda w, e: print(f"Load event: {e.value_nick}"))
# タイトル更新 (title-changed には引数が無いので、get_title() で読む)
web_view.connect("title-changed",
lambda w: window.set_title(f"{w.get_title()} - Browser"))ハンドラの引数は (web_view, load_event)
です。load_event は BlinkGTK.LoadEvent
の値です。
def on_load_changed(self, web_view, load_event):
"""読み込みの状態の変化 (状態の表示に使う)"""
if load_event == BlinkGTK.LoadEvent.COMMITTED:
self.status_label.set_text("表示する文書が確定しました")
elif load_event == BlinkGTK.LoadEvent.FINISHED:
self.status_label.set_text("完了")STARTED は load_uri() /
load_html()
を呼んだときだけ来ます。リンク・戻る・進む・再読み込みによる移動では来ませんSTARTED / FINISHED ではなく notify::is-loading
を使いますBlinkGTK.LoadEvent.REDIRECTED
は将来のために予約されており、現在は送られませんハンドラの引数は (web_view, error_code, failing_uri)
です。
def on_load_failed(self, web_view, error_code, failing_uri):
"""読み込みが確定せずに終わった"""
# iframe など、表示中のページではない失敗は、UI に触る前に除く
if failing_uri != (web_view.get_uri() or failing_uri):
return
if error_code == -3:
# 中断。停止ボタンや、読み込み中に別のページを開いたときに普通に起きる
self.status_label.set_text("中断しました")
return
self.status_label.set_text(f"読み込めませんでした (エラー番号 {error_code}): {failing_uri}")error_code は Chromium のネットワークエラーの番号
(負の値) です。BlinkGTK.LoadError の値ではありません-3) ですload-failed ではなく
COMMITTED と FINISHED
が来ます。エラーページを表示した場合も、FINISHED
が届きますfailing_uri と
get_uri() を比べて先に除きます (中断の -3 が
iframe から届くこともあります)ハンドラの引数は (web_view) だけです。新しいタイトルは
get_title() で読みます。
def on_title_changed(self, web_view):
"""タイトルの変更"""
title = web_view.get_title()
self.set_title(f"{title} - Python Browser" if title else "Python Browser")ハンドラの引数は (web_view) だけです。新しい URI は
get_uri() で読みます。
def on_uri_changed(self, web_view):
"""URI の変更"""
uri = web_view.get_uri() or ""
self.url_entry.set_text(uri)
# 接続の種類を表示する
if uri.startswith("https://"):
self.security_label.set_text("HTTPS")
elif uri.startswith("http://"):
self.security_label.set_text("HTTP")
else:
self.security_label.set_text("")is-loading プロパティの変化は
notify::is-loading で届きます。ハンドラの引数は
(web_view, pspec) で、値は
web_view.is_loading() で読みます。
リンク・戻る・進む・再読み込みを含むどの移動でも、読み込みの開始と終了で届きます。
完了・失敗・中断のどの終わり方でも、終わると False
に戻ります。
def on_is_loading_changed(self, web_view, _pspec):
"""読み込み中の表示と、停止・再読み込みのボタンを切り替える"""
loading = web_view.is_loading()
self.progress_bar.set_visible(loading)
if loading:
self.progress_bar.pulse()
self.stop_button.set_sensitive(loading)
self.reload_button.set_sensitive(not loading)
self.back_button.set_sensitive(web_view.can_go_back())
self.forward_button.set_sensitive(web_view.can_go_forward())
web_view.connect("notify::is-loading", self.on_is_loading_changed)開始のときは続けて 2 回届くことがあるので、届いた回数ではなく値で判定してください。
title と uri
もプロパティなので、notify::title と
notify::uri でも変化を受け取れます。
web_view.connect("notify::title", lambda w, pspec: print(f"title = {w.props.title}"))GObject
のシグナルのハンドラで例外が起きても、プログラムは止まらず、例外の内容が表示されるだけです。
どのシグナルで起きたかを分かりやすくするには、デコレータで包みます。
import sys
from functools import wraps
from typing import Callable
def signal_handler(signal_name: str) -> Callable:
"""ハンドラの中で起きた例外を、シグナル名を付けて表示する"""
def decorator(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"{signal_name} のハンドラで例外: {e!r}", file=sys.stderr)
return wrapper
return decorator
class BrowserWindow(Gtk.ApplicationWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.web_view = BlinkGTK.WebView.new()
self.set_child(self.web_view)
self.web_view.connect("load-changed", self.on_load_changed)
self.web_view.connect("load-failed", self.on_load_failed)
@signal_handler("load-changed")
def on_load_changed(self, web_view, load_event):
if load_event == BlinkGTK.LoadEvent.FINISHED:
print("読み込み完了")
@signal_handler("load-failed")
def on_load_failed(self, web_view, error_code, failing_uri):
print(f"確定せずに終了: {failing_uri} (エラー番号 {error_code})")connect() が返す番号を覚えておくと、後で
disconnect() で外せます。
from typing import Callable, Dict, List
class SignalManager:
"""WebView に接続したハンドラを、シグナル名ごとに覚えておく"""
def __init__(self, web_view: BlinkGTK.WebView):
self.web_view = web_view
self.handlers: Dict[str, List[int]] = {}
def connect(self, signal_name: str, handler: Callable) -> int:
handler_id = self.web_view.connect(signal_name, handler)
self.handlers.setdefault(signal_name, []).append(handler_id)
return handler_id
def disconnect(self, signal_name: str) -> None:
for handler_id in self.handlers.pop(signal_name, []):
self.web_view.disconnect(handler_id)
def disconnect_all(self) -> None:
for signal_name in list(self.handlers):
self.disconnect(signal_name)
# 使用例
signal_manager = SignalManager(web_view)
signal_manager.connect("load-changed", on_load_changed)
signal_manager.connect("title-changed", on_title_changed)
# 後で外す
signal_manager.disconnect_all()BlinkGTK.gtk_application_run() が回すのは GLib
のメインループです。
asyncio.run() などで動かす asyncio
のイベントループはこの間は動かないため、asyncio.Event
を待って読み込みの完了を待つ書き方は、そのままでは使えません。
読み込みの完了を待ってから次の処理をするには、notify::is-loading
で is-loading が False
に戻ったときに次の処理を始めます。
失敗した場合は、False に戻る前に load-failed
が届くので、その内容を覚えておきます。
次のプログラムは、引数に渡した URL を 1 つずつ順に読み込み、30
秒で終わらない読み込みは止めます。
すべて終わるとウィンドウを閉じ、プログラムが終了します。
#!/usr/bin/env python3
"""複数の URL を 1 つずつ順に読み込み、終わったらウィンドウを閉じる"""
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
TIMEOUT_SECONDS = 30
class SequentialLoader:
def __init__(self, window, view, urls):
self.window = window
self.view = view
self.urls = list(urls)
self.current = None
self.timeout_id = 0
self.started = False
self.error_code = None
self.timed_out = False
# 読み込みの終わりは notify::is-loading で受け取る。完了・失敗・中断のどの終わり方でも
# is-loading は False に戻る。失敗した場合は、その前に load-failed が届く
view.connect("notify::is-loading", self.on_is_loading_changed)
view.connect("load-failed", self.on_load_failed)
def load_next(self):
if not self.urls:
print("すべて読み込みました", flush=True)
self.window.close() # 最後のウィンドウを閉じると gtk_application_run() が戻る
return GLib.SOURCE_REMOVE
self.current = self.urls.pop(0)
self.started = False
self.error_code = None
self.timed_out = False
self.timeout_id = GLib.timeout_add_seconds(TIMEOUT_SECONDS, self.on_timeout)
self.view.load_uri(self.current)
return GLib.SOURCE_REMOVE
def done(self, message):
if self.timeout_id:
GLib.source_remove(self.timeout_id)
self.timeout_id = 0
print(f"{message}: {self.current}", flush=True)
# シグナルの処理の中で次の load_uri() を呼ばず、メインループに戻ってから呼ぶ
GLib.idle_add(self.load_next)
def on_is_loading_changed(self, view, _pspec):
if view.is_loading():
self.started = True
elif self.started:
self.started = False
if self.timed_out:
self.done("時間切れで止めました")
elif self.error_code is not None:
self.done(f"確定せずに終了 (エラー番号 {self.error_code})")
else:
self.done(f"完了 ({view.get_title() or 'タイトルなし'})")
def on_load_failed(self, view, error_code, failing_uri):
# iframe などの失敗は除く。Chromium は URL を正規化して通知する (末尾の / など) ので、
# 入力した文字列ではなく、表示しようとしている URI と比べる
if failing_uri in (view.get_uri(), self.current) or \
failing_uri.rstrip("/") == self.current.rstrip("/"):
self.error_code = error_code
def on_timeout(self):
self.timeout_id = 0
self.timed_out = True
print(f"{TIMEOUT_SECONDS} 秒で終わらないので止めます: {self.current}", flush=True)
self.view.stop() # 止めた後も is-loading が False に戻ってから次へ進む
return GLib.SOURCE_REMOVE
def on_activate(app, urls):
window = Gtk.ApplicationWindow(application=app, title="Sequential Loader")
window.set_default_size(800, 600)
view = BlinkGTK.WebView.new()
window.set_child(view)
window.present()
loader = SequentialLoader(window, view, urls)
window.loader = loader # ウィンドウが生きている間 loader を保持する
loader.load_next()
def main():
urls = sys.argv[1:] or ["https://example.com/", "https://www.gtk.org/"]
ok, _argv = BlinkGTK.gtk_init(sys.argv[:1])
if not ok:
print("BlinkGTK を初期化できませんでした", file=sys.stderr)
return 1
app = Gtk.Application(application_id="org.example.SequentialLoader")
app.connect("activate", on_activate, urls)
rc = BlinkGTK.gtk_application_run(app)
BlinkGTK.gtk_shutdown()
return rc
if __name__ == "__main__":
sys.exit(main())実行例:
blinkgtk-run python3 sequential_loader.py https://example.com/ https://www.gtk.org/FINISHED
を使わないのは、確定せずに終わった読み込み (止めた読み込みなど) では
FINISHED が来ないためです。頁の移動が確定する前に
stop() で止めると、load-failed (エラー番号
-3) の後に is-loading が False
に戻ります。確定した後に止めた場合は load-failed
が来ないことがあるので、時間切れは自分で記録して表示を分けていますload_uri() は、シグナルのハンドラの中ではなく
GLib.idle_add() でメインループに戻ってから呼んでいます4 つのシグナルと、JavaScript・画像・ローカルストレージの切り替えを使うブラウザです。
#!/usr/bin/env python3
"""4 つのシグナルと設定の切り替えを使うブラウザ"""
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 CompleteBrowser(Gtk.ApplicationWindow):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.set_title("Complete Browser (Python)")
self.set_default_size(1200, 800)
self._build_ui()
self._connect_signals()
def _build_ui(self):
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
# ツールバー
toolbar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
for side in ("start", "end", "top", "bottom"):
getattr(toolbar, f"set_margin_{side}")(6)
self.back_button = Gtk.Button(label="←", sensitive=False)
self.forward_button = Gtk.Button(label="→", sensitive=False)
self.reload_button = Gtk.Button(label="↻")
self.stop_button = Gtk.Button(label="×", sensitive=False)
self.security_label = Gtk.Label(label="")
self.url_entry = Gtk.Entry(hexpand=True)
for widget in (self.back_button, self.forward_button, self.reload_button,
self.stop_button, self.security_label, self.url_entry):
toolbar.append(widget)
# 設定のスイッチ
settings_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
settings_box.set_margin_start(6)
settings_box.set_margin_bottom(6)
self.js_switch = Gtk.Switch(active=True)
self.images_switch = Gtk.Switch(active=True)
self.storage_switch = Gtk.Switch(active=True)
for label, switch in (("JavaScript:", self.js_switch),
("画像:", self.images_switch),
("ローカルストレージ:", self.storage_switch)):
settings_box.append(Gtk.Label(label=label))
settings_box.append(switch)
self.progress_bar = Gtk.ProgressBar(visible=False)
self.web_view = BlinkGTK.WebView.new()
self.web_view.set_vexpand(True)
self.status_label = Gtk.Label(label="準備完了", xalign=0.0)
self.status_label.set_margin_start(6)
self.status_label.set_margin_bottom(3)
for widget in (toolbar, settings_box, self.progress_bar,
self.web_view, self.status_label):
vbox.append(widget)
self.set_child(vbox)
def _connect_signals(self):
self.web_view.connect("notify::is-loading", self.on_is_loading_changed)
self.web_view.connect("load-changed", self.on_load_changed)
self.web_view.connect("load-failed", self.on_load_failed)
self.web_view.connect("title-changed", self.on_title_changed)
self.web_view.connect("uri-changed", self.on_uri_changed)
self.back_button.connect("clicked", lambda _b: self.web_view.go_back())
self.forward_button.connect("clicked", lambda _b: self.web_view.go_forward())
self.reload_button.connect("clicked", lambda _b: self.web_view.reload())
self.stop_button.connect("clicked", lambda _b: self.web_view.stop())
self.url_entry.connect("activate", self.on_url_activate)
# 設定は次の読み込みから使われるので、変えたら読み込み直す
self.js_switch.connect("notify::active", self.on_setting_toggled,
self.web_view.set_javascript_enabled)
self.images_switch.connect("notify::active", self.on_setting_toggled,
self.web_view.set_images_enabled)
self.storage_switch.connect("notify::active", self.on_setting_toggled,
self.web_view.set_local_storage_enabled)
def on_setting_toggled(self, switch, _pspec, setter):
setter(switch.get_active())
if self.web_view.get_uri():
self.web_view.reload()
def on_is_loading_changed(self, web_view, _pspec):
"""読み込み中の表示とボタン (リンク・戻る・再読み込みを含め、どの移動でも来る)"""
loading = web_view.is_loading()
self.progress_bar.set_visible(loading)
if loading:
self.progress_bar.pulse()
self.status_label.set_text("読み込み中")
self.stop_button.set_sensitive(loading)
self.reload_button.set_sensitive(not loading)
self.back_button.set_sensitive(web_view.can_go_back())
self.forward_button.set_sensitive(web_view.can_go_forward())
def on_load_changed(self, web_view, load_event):
"""状態の表示だけに使う"""
if load_event == BlinkGTK.LoadEvent.FINISHED:
self.status_label.set_text("完了")
def on_load_failed(self, web_view, error_code, failing_uri):
# iframe など、表示中のページではない失敗は、UI に触る前に除く
if failing_uri != (web_view.get_uri() or failing_uri):
return
if error_code == -3:
self.status_label.set_text("中断しました")
else:
self.status_label.set_text(f"読み込めませんでした (エラー番号 {error_code}): {failing_uri}")
def on_title_changed(self, web_view):
title = web_view.get_title()
self.set_title(f"{title} - Complete Browser" if title else "Complete Browser")
def on_uri_changed(self, web_view):
uri = web_view.get_uri() or ""
self.url_entry.set_text(uri)
if uri.startswith("https://"):
self.security_label.set_text("HTTPS")
elif uri.startswith("http://"):
self.security_label.set_text("HTTP")
else:
self.security_label.set_text("")
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.web_view.load_uri(url)
def on_activate(app, url):
window = CompleteBrowser(application=app)
window.present()
window.web_view.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("BlinkGTK を初期化できませんでした", file=sys.stderr)
return 1
app = Gtk.Application(application_id="org.example.CompleteBrowser")
app.connect("activate", on_activate, url)
rc = BlinkGTK.gtk_application_run(app)
BlinkGTK.gtk_shutdown()
return rc
if __name__ == "__main__":
sys.exit(main())notify::is-loading
で、状態の表示は load-changed と load-failed
で更新していますreload() していますこのチュートリアルでは、Python で BlinkGTK のイベント処理を行う方法を学びました。
notify::is-loading
で切り替えること作成者: BlinkGTK開発チーム
ライセンス: BSD 3-Clause
フィードバック: daisy19@gmail.com