Last Updated: 2026-01-01
Target Version: BlinkGTK v0.9.28-dev or later
Level: Beginner to Advanced
Language: English |
This tutorial teaches event-driven programming using BlinkGTK's GObject signal system.
load-changed,
load-failed, title-changed,
uri-changed)# Required packages
- gtk4
- blinkgtk
# Compilation example
gcc your-program.c $(pkg-config --cflags --libs blinkgtk-0.1) -o your-programGObject signals are the core of GTK/GLib event-driven programming:
BlinkGTK Signals:
| Signal Name | Firing Timing | Usage |
|-------------|--------------|-------|
| load-changed | Load state changed | Progress display
|
| load-failed | Load failed | Error handling |
| title-changed | Title changed | Window title update
|
| uri-changed | URI changed | Address bar update |
Let's start with the simplest example:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* Signal handler */
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 = "Started";
break;
case BLINK_LOAD_REDIRECTED:
event_name = "Redirected";
break;
case BLINK_LOAD_COMMITTED:
event_name = "Committed";
break;
case BLINK_LOAD_FINISHED:
event_name = "Finished";
break;
}
g_print("Event: %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();
/* Connect signal */
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);
/* Note 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;
}Compile and Run:
gcc basic-signal.c $(pkg-config --cflags --libs blinkgtk-0.1) -o basic-signal
./basic-signalOutput:
Event: Started
Event: Committed
Event: Finished
How to pass additional data to signal handlers:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* Application data */
typedef struct {
GtkWidget* window;
GtkWidget* status_label;
int load_count;
} AppData;
/* Signal handler */
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),
"Loading...");
break;
case BLINK_LOAD_FINISHED:
data->load_count++;
char* message = g_strdup_printf("Done (#%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("Ready");
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);
/* Connect signal (pass data as 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);
/* Note 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;
}Key Points:
AppData structdata as 4th argument to
g_signal_connect()user_data inside handlerUpdate progress bar with load-changed signal:
#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),
"Loading started...");
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),
"Redirecting...");
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),
"Rendering...");
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), "Done");
/* Hide progress bar after 1 second */
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("Ready");
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);
/* Connect signal */
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);
/* Note 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;
}Behavior:
Display error dialog with load-failed signal:
#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);
/* Generate error message */
const char* error_message = "Unknown error";
switch (error_code) {
case -2:
error_message = "Failed to load page";
break;
case -7:
error_message = "Connection timed out";
break;
case -105:
error_message = "Server not found";
break;
case -106:
error_message = "No internet connection";
break;
}
/* Show error dialog */
GtkWidget* dialog = gtk_message_dialog_new(
window,
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"Loading Error"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"%s\n\nURL: %s\nError code: %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();
/* Connect signal */
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);
/* Test with non-existent 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);
/* Note 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;
}Update window title with title-changed signal:
static void on_title_changed(GtkWidget* web_view,
const char* title,
gpointer user_data) {
GtkWindow* window = GTK_WINDOW(user_data);
/* "Title - AppName" format */
char* window_title = g_strdup_printf("%s - MyBrowser", title);
gtk_window_set_title(window, window_title);
g_free(window_title);
g_print("Title changed: %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");
/* Connect signal */
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));
}Practical browser UI combining multiple signals:
#include <gtk/gtk.h>
#include <blink_gtk/blink_gtk.h>
/* Application data */
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: Update UI based on load state */
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:
/* Show progress bar */
gtk_progress_bar_set_fraction(
GTK_PROGRESS_BAR(ui->progress_bar), 0.0);
gtk_widget_set_visible(ui->progress_bar, TRUE);
/* Show stop button, hide reload button */
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), "Loading...");
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);
/* Show reload button, hide stop button */
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), "Done");
/* Update navigation button states */
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: Show error */
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("Error (code: %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: Update window title */
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: Update address bar */
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);
}
/* Button click handlers */
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);
}
/* Connect signals */
static void setup_signals(BrowserUI* ui) {
/* WebView signals */
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);
/* Button signals */
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);
/* Window */
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);
/* Toolbar */
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);
/* Progress bar */
ui->progress_bar = gtk_progress_bar_new();
gtk_widget_set_visible(ui->progress_bar, FALSE);
/* Status bar */
ui->status_label = gtk_label_new("Ready");
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();
/* Layout */
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);
/* Connect signals */
setup_signals(ui);
/* Initial 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);
/* Note 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;
}Compile:
gcc browser-ui.c $(pkg-config --cflags --libs blinkgtk-0.1) -o browser-ui
./browser-uiFeatures:
Complete browser with Settings API integration:
#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), "Loading...");
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), "Done");
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 = "Unknown error";
switch (error_code) {
case -2: error_msg = "Loading failed"; break;
case -7: error_msg = "Timed out"; break;
case -105: error_msg = "Server not found"; break;
case -106: error_msg = "Internet disconnected"; break;
}
char* message = g_strdup_printf("%s (code: %d)", error_msg, error_code);
gtk_label_set_text(GTK_LABEL(ui->status_label), message);
g_free(message);
/* Error dialog */
GtkWidget* dialog = gtk_message_dialog_new(
GTK_WINDOW(ui->window),
GTK_DIALOG_MODAL,
GTK_MESSAGE_ERROR,
GTK_BUTTONS_OK,
"Loading Error"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"%s\n\nURL: %s\nError code: %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;
/* Update address bar */
gtk_editable_set_text(GTK_EDITABLE(ui->url_entry), uri);
/* Update security icon */
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,
"This connection is secure");
} else {
gtk_image_set_from_icon_name(GTK_IMAGE(ui->security_icon),
"dialog-warning");
gtk_widget_set_tooltip_text(ui->security_icon,
"This connection is not secure");
}
}
/* Settings changes */
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 ? "enabled" : "disabled");
}
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("Image loading: %s\n", active ? "enabled" : "disabled");
}
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("Local storage: %s\n", active ? "enabled" : "disabled");
}
/* Button handlers */
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);
}
/* Signal connection */
static void setup_signals(AdvancedBrowserUI* ui) {
/* WebView signals */
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);
/* Buttons */
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);
/* Window */
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);
/* Toolbar */
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 panel */
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("Images:"));
gtk_box_append(GTK_BOX(settings_box), ui->images_switch);
gtk_box_append(GTK_BOX(settings_box), gtk_label_new("Storage:"));
gtk_box_append(GTK_BOX(settings_box), ui->storage_switch);
/* Progress bar */
ui->progress_bar = gtk_progress_bar_new();
gtk_widget_set_visible(ui->progress_bar, FALSE);
/* Status bar */
ui->status_label = gtk_label_new("Ready");
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();
/* Layout */
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);
/* Connect signals */
setup_signals(ui);
/* Initial 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);
/* Note 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;
}Compile:
gcc advanced-browser.c $(pkg-config --cflags --libs blinkgtk-0.1) -o advanced-browser
./advanced-browserFeatures:
Simple spinner display:
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);
}
}Simple history list:
typedef struct {
GList* history; // String list
int current_index;
} HistoryManager;
static void on_uri_changed(GtkWidget* web_view,
const char* uri,
gpointer user_data) {
HistoryManager* history = (HistoryManager*)user_data;
/* Remove forward history */
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);
}
/* Add new URI */
history->history = g_list_append(history->history, g_strdup(uri));
history->current_index = g_list_length(history->history) - 1;
}Title and simple favicon display:
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;
/* Truncate long titles */
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;
/* Lock icon for HTTPS pages */
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");
}
}Display error page with 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>Error</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>Unable to Load Page</h1>"
" <p>Failed to load the following URL:</p>"
" <p class='error-code'>%s</p>"
" <p>Error code: <span class='error-code'>%d</span></p>"
" <button onclick='location.href=\"%s\"'>Retry</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);
}Simple download detection (using load-failed):
static void on_load_failed(GtkWidget* web_view,
const char* failing_url,
int error_code,
gpointer user_data) {
/* Detect downloadable file types */
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,
"Download"
);
gtk_message_dialog_format_secondary_text(
GTK_MESSAGE_DIALOG(dialog),
"Do you want to download this file?\n\n%s",
failing_url
);
int response = gtk_dialog_run(GTK_DIALOG(dialog));
gtk_widget_destroy(dialog);
if (response == GTK_RESPONSE_YES) {
g_print("Download started: %s\n", failing_url);
/* Actual download implementation */
}
}
}Symptom: Signal handler not being called
Causes and Solutions:
Signal name typo
// No Wrong
g_signal_connect(web_view, "load_changed", ...); // Underscore
// Yes Correct
g_signal_connect(web_view, "load-changed", ...); // HyphenConnecting before WebView initialization
// No Wrong
g_signal_connect(web_view, "load-changed", ...);
web_view = blink_web_view_new();
// Yes Correct
web_view = blink_web_view_new();
g_signal_connect(web_view, "load-changed", ...);GTK main loop not running
blink_web_view_load_uri(BLINK_WEB_VIEW(web_view), "https://example.com");
// No Signals won't fire (no main loop)
blink_gtk_run_main_loop(); // Yes Start main loop (g_application_run() is forbidden)Symptom: Segmentation fault in signal handler
Solution:
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
/* NULL check */
if (user_data == NULL) {
g_warning("user_data is NULL");
return;
}
MyData* data = (MyData*)user_data;
/* ... */
}Symptom: Memory usage keeps growing
Causes and Solutions:
String leaks
/* No Memory leak */
const char* ua = blink_web_view_get_user_agent(web_view);
/* Yes Correct */
char* ua = blink_web_view_get_user_agent(web_view);
g_print("UA: %s\n", ua);
g_free(ua); // RequiredSignal handler not disconnected
/* Save handler ID */
gulong handler_id = g_signal_connect(web_view, "load-changed",
G_CALLBACK(on_load_changed), NULL);
/* Disconnect before destroying WebView */
g_signal_handler_disconnect(web_view, handler_id);Symptom: Progress bar stays at 0%
Solution:
static void on_load_changed(GtkWidget* web_view,
BlinkLoadEvent load_event,
gpointer user_data) {
ProgressData* data = (ProgressData*)user_data;
/* Handle all events */
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); // Don't forget to show
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;
}
}This tutorial covered event-driven programming with BlinkGTK's GObject signal system.
Complete sample code is available in
docs/04-user-guides/examples/:
minimal-browser.c - Minimal browserbrowser-with-signals.c - Using all signalscomplete-browser.c - Full-featured browserAuthor: BlinkGTK Development Team
License: BSD 3-Clause
Feedback: daisy19@gmail.com