最終更新: 2026-07-29
対象バージョン: BlinkGTK v1.1.0 以降
レベル: 初級〜上級
言語: 日本語 |
このチュートリアルでは、BlinkGTKのGObjectシグナルシステムを使用したイベント駆動プログラミングを学びます。
load-changed,
load-failed, title-changed,
uri-changed)# 必要なパッケージ
- gtk4
- blinkgtk
# コンパイル例
gcc your-program.c $(pkg-config --cflags --libs blinkgtk-0.1) -o your-programGObjectシグナルは、GTK/GLibのイベント駆動プログラミングの中核です:
BlinkGTKのシグナル:
| シグナル名 | 発火タイミング | 用途 |
|-----------|--------------|------|
| load-changed | ロード状態変化 | プログレス表示 |
| load-failed | ロード失敗 | エラー処理 |
| title-changed | タイトル変更 | ウィンドウタイトル更新
|
| uri-changed | URI変更 | アドレスバー更新 |
最もシンプルな例から始めましょう:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* シグナルハンドラ */
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
const char* event_name = "";
switch (load_event) {
case BLINK_LOAD_STARTED:
event_name = "読み込み開始";
break;
case BLINK_LOAD_REDIRECTED:
event_name = "リダイレクト";
break;
case BLINK_LOAD_COMMITTED:
event_name = "コミット";
break;
case BLINK_LOAD_FINISHED:
event_name = "読み込み完了";
break;
}
g_print("イベント: %s\n", event_name);
}
static void on_activate(GtkApplication* app, gpointer user_data) {
GtkWidget* window = gtk_application_window_new(app);
GtkWidget* web_view = blink_web_view_new();
/* シグナル接続 */
g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), NULL);
gtk_window_set_child(GTK_WINDOW(window), web_view);
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://example.com");
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.signals", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}コンパイルと実行:
gcc basic-signal.c $(pkg-config --cflags --libs blinkgtk-0.1) -o basic-signal
./basic-signal出力例:
イベント: 読み込み開始
イベント: コミット
イベント: 読み込み完了
シグナルハンドラに追加データを渡す方法:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* アプリケーションデータ */
typedef struct {
GtkWidget* window;
GtkWidget* status_label;
int load_count;
} AppData;
/* シグナルハンドラ */
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
AppData* data = (AppData*)user_data;
switch (load_event) {
case BLINK_LOAD_STARTED:
gtk_label_set_text(GTK_LABEL(data->status_label),
"読み込み中...");
break;
case BLINK_LOAD_FINISHED:
data->load_count++;
char* message = g_strdup_printf("完了 (%d回目)", data->load_count);
gtk_label_set_text(GTK_LABEL(data->status_label), message);
g_free(message);
break;
default:
break;
}
}
static void on_activate(GtkApplication* app, gpointer user_data) {
AppData* data = g_new0(AppData, 1);
data->window = gtk_application_window_new(app);
data->status_label = gtk_label_new("準備完了");
GtkWidget* web_view = blink_web_view_new();
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(GTK_BOX(vbox), data->status_label);
gtk_box_append(GTK_BOX(vbox), web_view);
/* シグナル接続(dataをuser_dataとして渡す) */
g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), data);
gtk_window_set_child(GTK_WINDOW(data->window), vbox);
gtk_window_set_default_size(GTK_WINDOW(data->window), 800, 600);
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://example.com");
gtk_window_present(GTK_WINDOW(data->window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.userdata", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}ポイント:
AppData構造体でアプリケーション状態を管理g_signal_connect()の第4引数にdataを渡すuser_dataをキャストload-changedシグナルでプログレスバーを更新:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
typedef struct {
GtkWidget* progress_bar;
GtkWidget* status_label;
} ProgressData;
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
ProgressData* data = (ProgressData*)user_data;
switch (load_event) {
case BLINK_LOAD_STARTED:
/* 0% */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 0.0);
gtk_widget_set_visible(data->progress_bar, TRUE);
gtk_label_set_text(GTK_LABEL(data->status_label),
"読み込み開始...");
break;
case BLINK_LOAD_REDIRECTED:
/* 30% */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 0.3);
gtk_label_set_text(GTK_LABEL(data->status_label),
"リダイレクト中...");
break;
case BLINK_LOAD_COMMITTED:
/* 60% */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 0.6);
gtk_label_set_text(GTK_LABEL(data->status_label),
"レンダリング中...");
break;
case BLINK_LOAD_FINISHED:
/* 100% */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 1.0);
gtk_label_set_text(GTK_LABEL(data->status_label), "完了");
/* 1秒後にプログレスバーを非表示 */
g_timeout_add(1000, (GSourceFunc)gtk_widget_hide,
data->progress_bar);
break;
}
}
static void on_activate(GtkApplication* app, gpointer user_data) {
ProgressData* data = g_new0(ProgressData, 1);
GtkWidget* window = gtk_application_window_new(app);
GtkWidget* web_view = blink_web_view_new();
data->progress_bar = gtk_progress_bar_new();
data->status_label = gtk_label_new("準備完了");
gtk_widget_set_visible(data->progress_bar, FALSE);
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(GTK_BOX(vbox), data->progress_bar);
gtk_box_append(GTK_BOX(vbox), data->status_label);
gtk_box_append(GTK_BOX(vbox), web_view);
/* シグナル接続 */
g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), data);
gtk_window_set_child(GTK_WINDOW(window), vbox);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://www.chromium.org");
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.progress", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}動作:
load-failedシグナルでエラーダイアログを表示:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
GtkWindow* window = GTK_WINDOW(user_data);
/* エラーメッセージを生成 */
const char* error_message = "不明なエラー";
switch (error_code) {
case -2:
error_message = "ページの読み込みに失敗しました";
break;
case -7:
error_message = "接続がタイムアウトしました";
break;
case -105:
error_message = "サーバーが見つかりませんでした";
break;
case -106:
error_message = "インターネットに接続されていません";
break;
}
/* エラーダイアログ表示 */
GtkWidget* dialog = gtk_message_dialog_new(
window,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"読み込みエラー"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"%s\n\nURL: %s\nエラーコード: %d",
error_message, failing_url, error_code
);
g_signal_connect(dialog, "response",
G_CALLBACK(gtk_window_destroy), NULL);
gtk_widget_show(dialog);
}
static void on_activate(GtkApplication* app, gpointer user_data) {
GtkWidget* window = gtk_application_window_new(app);
GtkWidget* web_view = blink_web_view_new();
/* シグナル接続 */
g_signal_connect(web_view, "load-failed",
G_CALLBACK(on_load_failed), window);
gtk_window_set_child(GTK_WINDOW(window), web_view);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
/* 存在しないURLでテスト */
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://this-does-not-exist.example");
gtk_window_present(GTK_WINDOW(window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.error", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}title-changedシグナルでウィンドウタイトルを更新:
static void on_title_changed(GtkWidget* web_view,
const char* title,
gpointer user_data) {
GtkWindow* window = GTK_WINDOW(user_data);
/* "タイトル - アプリ名" 形式 */
char* window_title = g_strdup_printf("%s - MyBrowser", title);
gtk_window_set_title(window, window_title);
g_free(window_title);
g_print("タイトル変更: %s\n", title);
}
static void on_activate(GtkApplication* app, gpointer user_data) {
GtkWidget* window = gtk_application_window_new(app);
GtkWidget* web_view = blink_web_view_new();
gtk_window_set_title(GTK_WINDOW(window), "MyBrowser");
/* シグナル接続 */
g_signal_connect(web_view, "title-changed",
G_CALLBACK(on_title_changed), window);
gtk_window_set_child(GTK_WINDOW(window), web_view);
gtk_window_set_default_size(GTK_WINDOW(window), 800, 600);
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://example.com");
gtk_window_present(GTK_WINDOW(window));
}複数のシグナルを組み合わせた実用的なブラウザUI:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* アプリケーションデータ */
typedef struct {
GtkWidget* window;
GtkWidget* url_entry;
GtkWidget* back_button;
GtkWidget* forward_button;
GtkWidget* reload_button;
GtkWidget* stop_button;
GtkWidget* progress_bar;
GtkWidget* status_label;
GtkWidget* web_view;
} BrowserUI;
/* load-changed: ロード状態でUI更新 */
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
switch (load_event) {
case BLINK_LOAD_STARTED:
/* プログレスバー表示 */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(ui->progress_bar), 0.0);
gtk_widget_set_visible(ui->progress_bar, TRUE);
/* 停止ボタン表示、リロードボタン非表示 */
gtk_widget_set_visible(ui->stop_button, TRUE);
gtk_widget_set_visible(ui->reload_button, FALSE);
gtk_label_set_text(GTK_LABEL(ui->status_label), "読み込み中...");
break;
case BLINK_LOAD_COMMITTED:
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(ui->progress_bar), 0.5);
break;
case BLINK_LOAD_FINISHED: {
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(ui->progress_bar), 1.0);
gtk_widget_set_visible(ui->progress_bar, FALSE);
/* リロードボタン表示、停止ボタン非表示 */
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_visible(ui->reload_button, TRUE);
gtk_label_set_text(GTK_LABEL(ui->status_label), "完了");
/* ナビゲーションボタンの状態更新 */
gboolean can_go_back = blink_web_view_can_go_back(BLINK_WEB_VIEW(web_view));
gboolean can_go_forward = blink_web_view_can_go_forward(BLINK_WEB_VIEW(web_view));
gtk_widget_set_sensitive(ui->back_button, can_go_back);
gtk_widget_set_sensitive(ui->forward_button, can_go_forward);
break;
}
default:
break;
}
}
/* load-failed: エラー表示 */
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
char* message = g_strdup_printf("エラー (コード: %d)", error_code);
gtk_label_set_text(GTK_LABEL(ui->status_label), message);
g_free(message);
gtk_widget_set_visible(ui->progress_bar, FALSE);
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_visible(ui->reload_button, TRUE);
}
/* title-changed: ウィンドウタイトル更新 */
static void on_title_changed(GtkWidget* web_view,
const char* title,
gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
char* window_title = g_strdup_printf("%s - MyBrowser", title);
gtk_window_set_title(GTK_WINDOW(ui->window), window_title);
g_free(window_title);
}
/* uri-changed: アドレスバー更新 */
static void on_uri_changed(GtkWidget* web_view,
const char* uri,
gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
gtk_editable_set_text(GTK_EDITABLE(ui->url_entry), uri);
}
/* ボタンクリックハンドラ */
static void on_back_clicked(GtkButton* button, gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
blink_web_view_go_back(BLINK_WEB_VIEW(ui->web_view));
}
static void on_forward_clicked(GtkButton* button, gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
blink_web_view_go_forward(BLINK_WEB_VIEW(ui->web_view));
}
static void on_reload_clicked(GtkButton* button, gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
blink_web_view_reload(BLINK_WEB_VIEW(ui->web_view));
}
static void on_stop_clicked(GtkButton* button, gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
blink_web_view_stop(BLINK_WEB_VIEW(ui->web_view));
}
static void on_url_activate(GtkEntry* entry, gpointer user_data) {
BrowserUI* ui = (BrowserUI*)user_data;
const char* url = gtk_editable_get_text(GTK_EDITABLE(entry));
blink_web_view_load_uri(BLINK_WEB_VIEW(ui->web_view), url);
}
/* シグナル接続 */
static void setup_signals(BrowserUI* ui) {
/* WebViewシグナル */
g_signal_connect(ui->web_view, "load-changed",
G_CALLBACK(on_load_changed), ui);
g_signal_connect(ui->web_view, "load-failed",
G_CALLBACK(on_load_failed), ui);
g_signal_connect(ui->web_view, "title-changed",
G_CALLBACK(on_title_changed), ui);
g_signal_connect(ui->web_view, "uri-changed",
G_CALLBACK(on_uri_changed), ui);
/* ボタンシグナル */
g_signal_connect(ui->back_button, "clicked",
G_CALLBACK(on_back_clicked), ui);
g_signal_connect(ui->forward_button, "clicked",
G_CALLBACK(on_forward_clicked), ui);
g_signal_connect(ui->reload_button, "clicked",
G_CALLBACK(on_reload_clicked), ui);
g_signal_connect(ui->stop_button, "clicked",
G_CALLBACK(on_stop_clicked), ui);
g_signal_connect(ui->url_entry, "activate",
G_CALLBACK(on_url_activate), ui);
}
static void on_activate(GtkApplication* app, gpointer user_data) {
BrowserUI* ui = g_new0(BrowserUI, 1);
/* ウィンドウ */
ui->window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(ui->window), "MyBrowser");
gtk_window_set_default_size(GTK_WINDOW(ui->window), 1024, 768);
/* ツールバー */
GtkWidget* toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
gtk_widget_set_margin_start(toolbar, 5);
gtk_widget_set_margin_end(toolbar, 5);
gtk_widget_set_margin_top(toolbar, 5);
gtk_widget_set_margin_bottom(toolbar, 5);
ui->back_button = gtk_button_new_from_icon_name("go-previous");
ui->forward_button = gtk_button_new_from_icon_name("go-next");
ui->reload_button = gtk_button_new_from_icon_name("view-refresh");
ui->stop_button = gtk_button_new_from_icon_name("process-stop");
ui->url_entry = gtk_entry_new();
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_sensitive(ui->back_button, FALSE);
gtk_widget_set_sensitive(ui->forward_button, FALSE);
gtk_widget_set_hexpand(ui->url_entry, TRUE);
gtk_box_append(GTK_BOX(toolbar), ui->back_button);
gtk_box_append(GTK_BOX(toolbar), ui->forward_button);
gtk_box_append(GTK_BOX(toolbar), ui->reload_button);
gtk_box_append(GTK_BOX(toolbar), ui->stop_button);
gtk_box_append(GTK_BOX(toolbar), ui->url_entry);
/* プログレスバー */
ui->progress_bar = gtk_progress_bar_new();
gtk_widget_set_visible(ui->progress_bar, FALSE);
/* ステータスバー */
ui->status_label = gtk_label_new("準備完了");
gtk_widget_set_halign(ui->status_label, GTK_ALIGN_START);
gtk_widget_set_margin_start(ui->status_label, 5);
gtk_widget_set_margin_bottom(ui->status_label, 2);
/* WebView */
ui->web_view = blink_web_view_new();
/* レイアウト */
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(GTK_BOX(vbox), toolbar);
gtk_box_append(GTK_BOX(vbox), ui->progress_bar);
gtk_box_append(GTK_BOX(vbox), ui->web_view);
gtk_box_append(GTK_BOX(vbox), ui->status_label);
gtk_widget_set_vexpand(ui->web_view, TRUE);
gtk_window_set_child(GTK_WINDOW(ui->window), vbox);
/* シグナル接続 */
setup_signals(ui);
/* 初期URL */
blink_web_view_load_uri(BLINK_WEB_VIEW(ui->web_view), "https://www.chromium.org");
gtk_window_present(GTK_WINDOW(ui->window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.browser", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}コンパイル:
gcc browser-ui.c $(pkg-config --cflags --libs blinkgtk-0.1) -o browser-ui
./browser-ui機能:
Settings APIを統合した完全なブラウザ:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
typedef struct {
GtkWidget* window;
GtkWidget* url_entry;
GtkWidget* back_button;
GtkWidget* forward_button;
GtkWidget* reload_button;
GtkWidget* stop_button;
GtkWidget* progress_bar;
GtkWidget* status_label;
GtkWidget* security_icon;
GtkWidget* web_view;
/* Settings */
GtkWidget* js_switch;
GtkWidget* images_switch;
GtkWidget* storage_switch;
} AdvancedBrowserUI;
/* load-changed */
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
switch (load_event) {
case BLINK_LOAD_STARTED:
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->progress_bar), 0.0);
gtk_widget_set_visible(ui->progress_bar, TRUE);
gtk_widget_set_visible(ui->stop_button, TRUE);
gtk_widget_set_visible(ui->reload_button, FALSE);
gtk_label_set_text(GTK_LABEL(ui->status_label), "読み込み中...");
break;
case BLINK_LOAD_COMMITTED:
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->progress_bar), 0.5);
break;
case BLINK_LOAD_FINISHED:
gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->progress_bar), 1.0);
gtk_widget_set_visible(ui->progress_bar, FALSE);
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_visible(ui->reload_button, TRUE);
gtk_label_set_text(GTK_LABEL(ui->status_label), "完了");
gboolean can_back = blink_web_view_can_go_back(BLINK_WEB_VIEW(web_view));
gboolean can_forward = blink_web_view_can_go_forward(BLINK_WEB_VIEW(web_view));
gtk_widget_set_sensitive(ui->back_button, can_back);
gtk_widget_set_sensitive(ui->forward_button, can_forward);
break;
default:
break;
}
}
/* load-failed */
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
const char* error_msg = "不明なエラー";
switch (error_code) {
case -2: error_msg = "読み込み失敗"; break;
case -7: error_msg = "タイムアウト"; break;
case -105: error_msg = "サーバーが見つかりません"; break;
case -106: error_msg = "インターネット未接続"; break;
}
char* message = g_strdup_printf("%s (コード: %d)", error_msg, error_code);
gtk_label_set_text(GTK_LABEL(ui->status_label), message);
g_free(message);
/* エラーダイアログ */
GtkWidget* dialog = gtk_message_dialog_new(
GTK_WINDOW(ui->window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"読み込みエラー"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"%s\n\nURL: %s\nエラーコード: %d",
error_msg, failing_url, error_code
);
g_signal_connect(dialog, "response", G_CALLBACK(gtk_window_destroy), NULL);
gtk_widget_show(dialog);
gtk_widget_set_visible(ui->progress_bar, FALSE);
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_visible(ui->reload_button, TRUE);
}
/* title-changed */
static void on_title_changed(GtkWidget* web_view,
const char* title,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
char* window_title = g_strdup_printf("%s - AdvancedBrowser", title);
gtk_window_set_title(GTK_WINDOW(ui->window), window_title);
g_free(window_title);
}
/* uri-changed */
static void on_uri_changed(GtkWidget* web_view,
const char* uri,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
/* アドレスバー更新 */
gtk_editable_set_text(GTK_EDITABLE(ui->url_entry), uri);
/* セキュリティアイコン更新 */
if (g_str_has_prefix(uri, "https://")) {
gtk_image_set_from_icon_name(GTK_IMAGE(ui->security_icon),
"dialog-password");
gtk_widget_set_tooltip_text(ui->security_icon,
"このページは安全です");
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(ui->security_icon),
"dialog-warning");
gtk_widget_set_tooltip_text(ui->security_icon,
"このページは保護されていません");
}
}
/* Settings変更 */
static void on_js_switch_changed(GtkSwitch* widget, GParamSpec* pspec,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
gboolean active = gtk_switch_get_active(widget);
blink_web_view_set_javascript_enabled(BLINK_WEB_VIEW(ui->web_view), active);
g_print("JavaScript: %s\n", active ? "有効" : "無効");
}
static void on_images_switch_changed(GtkSwitch* widget, GParamSpec* pspec,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
gboolean active = gtk_switch_get_active(widget);
blink_web_view_set_images_enabled(BLINK_WEB_VIEW(ui->web_view), active);
g_print("画像読み込み: %s\n", active ? "有効" : "無効");
}
static void on_storage_switch_changed(GtkSwitch* widget, GParamSpec* pspec,
gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
gboolean active = gtk_switch_get_active(widget);
blink_web_view_set_local_storage_enabled(BLINK_WEB_VIEW(ui->web_view), active);
g_print("ローカルストレージ: %s\n", active ? "有効" : "無効");
}
/* ボタンハンドラ */
static void on_back_clicked(GtkButton* button, gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
blink_web_view_go_back(BLINK_WEB_VIEW(ui->web_view));
}
static void on_forward_clicked(GtkButton* button, gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
blink_web_view_go_forward(BLINK_WEB_VIEW(ui->web_view));
}
static void on_reload_clicked(GtkButton* button, gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
blink_web_view_reload(BLINK_WEB_VIEW(ui->web_view));
}
static void on_stop_clicked(GtkButton* button, gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
blink_web_view_stop(BLINK_WEB_VIEW(ui->web_view));
}
static void on_url_activate(GtkEntry* entry, gpointer user_data) {
AdvancedBrowserUI* ui = (AdvancedBrowserUI*)user_data;
const char* url = gtk_editable_get_text(GTK_EDITABLE(entry));
blink_web_view_load_uri(BLINK_WEB_VIEW(ui->web_view), url);
}
/* シグナル接続 */
static void setup_signals(AdvancedBrowserUI* ui) {
/* WebViewシグナル */
g_signal_connect(ui->web_view, "load-changed",
G_CALLBACK(on_load_changed), ui);
g_signal_connect(ui->web_view, "load-failed",
G_CALLBACK(on_load_failed), ui);
g_signal_connect(ui->web_view, "title-changed",
G_CALLBACK(on_title_changed), ui);
g_signal_connect(ui->web_view, "uri-changed",
G_CALLBACK(on_uri_changed), ui);
/* ボタン */
g_signal_connect(ui->back_button, "clicked",
G_CALLBACK(on_back_clicked), ui);
g_signal_connect(ui->forward_button, "clicked",
G_CALLBACK(on_forward_clicked), ui);
g_signal_connect(ui->reload_button, "clicked",
G_CALLBACK(on_reload_clicked), ui);
g_signal_connect(ui->stop_button, "clicked",
G_CALLBACK(on_stop_clicked), ui);
g_signal_connect(ui->url_entry, "activate",
G_CALLBACK(on_url_activate), ui);
/* Settings */
g_signal_connect(ui->js_switch, "notify::active",
G_CALLBACK(on_js_switch_changed), ui);
g_signal_connect(ui->images_switch, "notify::active",
G_CALLBACK(on_images_switch_changed), ui);
g_signal_connect(ui->storage_switch, "notify::active",
G_CALLBACK(on_storage_switch_changed), ui);
}
static void on_activate(GtkApplication* app, gpointer user_data) {
AdvancedBrowserUI* ui = g_new0(AdvancedBrowserUI, 1);
/* ウィンドウ */
ui->window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(ui->window), "AdvancedBrowser");
gtk_window_set_default_size(GTK_WINDOW(ui->window), 1200, 800);
/* ツールバー */
GtkWidget* toolbar = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
gtk_widget_set_margin_start(toolbar, 5);
gtk_widget_set_margin_end(toolbar, 5);
gtk_widget_set_margin_top(toolbar, 5);
gtk_widget_set_margin_bottom(toolbar, 5);
ui->back_button = gtk_button_new_from_icon_name("go-previous");
ui->forward_button = gtk_button_new_from_icon_name("go-next");
ui->reload_button = gtk_button_new_from_icon_name("view-refresh");
ui->stop_button = gtk_button_new_from_icon_name("process-stop");
ui->security_icon = gtk_image_new_from_icon_name("dialog-warning");
ui->url_entry = gtk_entry_new();
gtk_widget_set_visible(ui->stop_button, FALSE);
gtk_widget_set_sensitive(ui->back_button, FALSE);
gtk_widget_set_sensitive(ui->forward_button, FALSE);
gtk_widget_set_hexpand(ui->url_entry, TRUE);
gtk_box_append(GTK_BOX(toolbar), ui->back_button);
gtk_box_append(GTK_BOX(toolbar), ui->forward_button);
gtk_box_append(GTK_BOX(toolbar), ui->reload_button);
gtk_box_append(GTK_BOX(toolbar), ui->stop_button);
gtk_box_append(GTK_BOX(toolbar), ui->security_icon);
gtk_box_append(GTK_BOX(toolbar), ui->url_entry);
/* Settings パネル */
GtkWidget* settings_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_widget_set_margin_start(settings_box, 5);
gtk_widget_set_margin_end(settings_box, 5);
gtk_widget_set_margin_bottom(settings_box, 5);
ui->js_switch = gtk_switch_new();
ui->images_switch = gtk_switch_new();
ui->storage_switch = gtk_switch_new();
gtk_switch_set_active(GTK_SWITCH(ui->js_switch), TRUE);
gtk_switch_set_active(GTK_SWITCH(ui->images_switch), TRUE);
gtk_switch_set_active(GTK_SWITCH(ui->storage_switch), TRUE);
gtk_box_append(GTK_BOX(settings_box), gtk_label_new("JavaScript:"));
gtk_box_append(GTK_BOX(settings_box), ui->js_switch);
gtk_box_append(GTK_BOX(settings_box), gtk_label_new("画像:"));
gtk_box_append(GTK_BOX(settings_box), ui->images_switch);
gtk_box_append(GTK_BOX(settings_box), gtk_label_new("ストレージ:"));
gtk_box_append(GTK_BOX(settings_box), ui->storage_switch);
/* プログレスバー */
ui->progress_bar = gtk_progress_bar_new();
gtk_widget_set_visible(ui->progress_bar, FALSE);
/* ステータスバー */
ui->status_label = gtk_label_new("準備完了");
gtk_widget_set_halign(ui->status_label, GTK_ALIGN_START);
gtk_widget_set_margin_start(ui->status_label, 5);
gtk_widget_set_margin_bottom(ui->status_label, 2);
/* WebView */
ui->web_view = blink_web_view_new();
/* レイアウト */
GtkWidget* vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_box_append(GTK_BOX(vbox), toolbar);
gtk_box_append(GTK_BOX(vbox), settings_box);
gtk_box_append(GTK_BOX(vbox), ui->progress_bar);
gtk_box_append(GTK_BOX(vbox), ui->web_view);
gtk_box_append(GTK_BOX(vbox), ui->status_label);
gtk_widget_set_vexpand(ui->web_view, TRUE);
gtk_window_set_child(GTK_WINDOW(ui->window), vbox);
/* シグナル接続 */
setup_signals(ui);
/* 初期URL */
blink_web_view_load_uri(BLINK_WEB_VIEW(ui->web_view), "https://www.chromium.org");
gtk_window_present(GTK_WINDOW(ui->window));
}
int main(int argc, char* argv[]) {
blink_gtk_init(&argc, &argv); /* GTK4 は引数なし */
GtkApplication* app = gtk_application_new("org.example.advanced", 0);
g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL);
/* 注意 g_application_run() は使用禁止 — Chromium RunLoop が飢餓状態になる
* g_application_register/activate で GtkApplication シグナルを発火させてから
* blink_gtk_run_main_loop() でChromium RunLoop を起動する */
g_application_register(G_APPLICATION(app), NULL, NULL);
g_application_activate(G_APPLICATION(app));
int status = blink_gtk_run_main_loop();
g_object_unref(app);
blink_gtk_shutdown();
return status;
}コンパイル:
gcc advanced-browser.c $(pkg-config --cflags --libs blinkgtk-0.1) -o advanced-browser
./advanced-browser機能:
シンプルなスピナー表示:
typedef struct {
GtkWidget* spinner;
GtkWidget* web_view;
} SpinnerData;
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
SpinnerData* data = (SpinnerData*)user_data;
if (load_event == BLINK_LOAD_STARTED) {
gtk_spinner_start(GTK_SPINNER(data->spinner));
gtk_widget_set_visible(data->spinner, TRUE);
} else if (load_event == BLINK_LOAD_FINISHED) {
gtk_spinner_stop(GTK_SPINNER(data->spinner));
gtk_widget_set_visible(data->spinner, FALSE);
}
}シンプルな履歴リスト:
typedef struct {
GList* history; // 文字列リスト
int current_index;
} HistoryManager;
static void on_uri_changed(GtkWidget* web_view,
const char* uri,
gpointer user_data) {
HistoryManager* history = (HistoryManager*)user_data;
/* 現在位置より先の履歴を削除 */
while (history->current_index < g_list_length(history->history) - 1) {
GList* last = g_list_last(history->history);
g_free(last->data);
history->history = g_list_delete_link(history->history, last);
}
/* 新しいURIを追加 */
history->history = g_list_append(history->history, g_strdup(uri));
history->current_index = g_list_length(history->history) - 1;
}タイトルと簡易的なfavicon表示:
typedef struct {
GtkWidget* tab_label;
GtkWidget* tab_icon;
} TabData;
static void on_title_changed(GtkWidget* web_view,
const char* title,
gpointer user_data) {
TabData* tab = (TabData*)user_data;
/* タイトルを省略表示 */
char* display_title;
if (strlen(title) > 20) {
display_title = g_strdup_printf("%.17s...", title);
} else {
display_title = g_strdup(title);
}
gtk_label_set_text(GTK_LABEL(tab->tab_label), display_title);
g_free(display_title);
}
static void on_uri_changed(GtkWidget* web_view,
const char* uri,
gpointer user_data) {
TabData* tab = (TabData*)user_data;
/* HTTPSページにはロックアイコン */
if (g_str_has_prefix(uri, "https://")) {
gtk_image_set_from_icon_name(GTK_IMAGE(tab->tab_icon),
"dialog-password");
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(tab->tab_icon),
"text-html");
}
}HTMLでエラーページ表示:
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
char* error_html = g_strdup_printf(
"<!DOCTYPE html>"
"<html>"
"<head>"
" <meta charset='UTF-8'>"
" <title>エラー</title>"
" <style>"
" body {"
" font-family: sans-serif;"
" text-align: center;"
" padding: 50px;"
" background: #f5f5f5;"
" }"
" h1 { color: #d32f2f; }"
" .error-code { color: #666; font-family: monospace; }"
" button {"
" padding: 10px 20px;"
" font-size: 16px;"
" background: #1976d2;"
" color: white;"
" border: none;"
" border-radius: 4px;"
" cursor: pointer;"
" }"
" button:hover { background: #1565c0; }"
" </style>"
"</head>"
"<body>"
" <h1>ページを読み込めません</h1>"
" <p>次のURLの読み込みに失敗しました:</p>"
" <p class='error-code'>%s</p>"
" <p>エラーコード: <span class='error-code'>%d</span></p>"
" <button onclick='location.href=\"%s\"'>再試行</button>"
"</body>"
"</html>",
failing_url, error_code, failing_url
);
blink_web_view_load_html(BLINK_WEB_VIEW(web_view), error_html, "about:error");
g_free(error_html);
}簡易的なダウンロード検出(load-failedで代用):
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
/* ダウンロード可能なファイルタイプを検出 */
if (g_str_has_suffix(failing_url, ".pdf") || g_str_has_suffix(failing_url, ".zip") ||
g_str_has_suffix(failing_url, ".tar.gz")) {
GtkWidget* dialog = gtk_message_dialog_new(
NULL,
GTK_DIALOG_MODAL,
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_YES_NO,
"ダウンロード"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"このファイルをダウンロードしますか?\n\n%s",
failing_url
);
int response = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
if (response == GTK_RESPONSE_YES) {
g_print("ダウンロード開始: %s\n", failing_url);
/* 実際のダウンロード処理 */
}
}
}症状: シグナルハンドラが呼ばれない
原因と対策:
シグナル名のタイプミス
// 非対応 間違い
g_signal_connect(web_view, "load_changed", ...); // アンダースコア
// 対応 正しい
g_signal_connect(web_view, "load-changed", ...); // ハイフンWebViewが初期化される前に接続
// 非対応 間違い
g_signal_connect(web_view, "load-changed", ...);
web_view = blink_web_view_new();
// 対応 正しい
web_view = blink_web_view_new();
g_signal_connect(web_view, "load-changed", ...);GTKメインループが動いていない
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://example.com");
// 非対応 シグナルは発火しない(メインループがない)
blink_gtk_run_main_loop(); // 対応 メインループ開始(g_application_run()は使用禁止)症状: シグナルハンドラ内でSegmentation fault
対策:
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
/* NULLチェック */
if (user_data == NULL) {
g_warning("user_data is NULL");
return;
}
MyData* data = (MyData*)user_data;
/* ... */
}症状: メモリ使用量が増え続ける
原因と対策:
文字列のリーク
/* 非対応 メモリリーク */
const char* ua = blink_web_view_get_user_agent(web_view);
/* 対応 正しい */
char* ua = blink_web_view_get_user_agent(web_view);
g_print("UA: %s\n", ua);
g_free(ua); // 必須シグナルハンドラが切断されていない
/* ハンドラIDを保存 */
gulong handler_id = g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), NULL);
/* WebView破棄前に切断 */
g_signal_handler_disconnect(web_view, handler_id);症状: プログレスバーが0%のまま
対策:
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
ProgressData* data = (ProgressData*)user_data;
/* すべてのイベントを処理 */
switch (load_event) {
case BLINK_LOAD_STARTED:
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 0.0);
gtk_widget_set_visible(data->progress_bar, TRUE); // 表示を忘れずに
break;
case BLINK_LOAD_COMMITTED:
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 0.5);
break;
case BLINK_LOAD_FINISHED:
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(data->progress_bar), 1.0);
break;
}
}このチュートリアルでは、BlinkGTKのGObjectシグナルシステムを使用したイベント駆動プログラミングを学びました。
完全なサンプルコードはdocs/04-user-guides/examples/にあります:
minimal-browser.c - 最小限のブラウザbrowser-with-signals.c - 全シグナルを使用complete-browser.c - 完全な機能を持つブラウザ作成者: BlinkGTK開発チーム
ライセンス: BSD 3-Clause
フィードバック: daisy19@gmail.com