BlinkGTK Rust (gtk4-rs) チュートリアル

Rust言語バインディング対応


目次

  1. はじめに
  2. gtk4-rsのセットアップ
  3. 最小限のブラウザ
  4. シグナル処理
  5. プロパティ監視
  6. ナビゲーション機能
  7. 実践的な使い方
  8. トラブルシューティング

はじめに

BlinkGTKはGObject Introspectionに対応しているため、Rustからgtk4-rsを通じて利用できます。Rustの型安全性とゼロコスト抽象化により、安全で高速なブラウザアプリケーションを構築できます。

BlinkGTKの特徴

前提条件


gtk4-rsのセットアップ

必要なパッケージのインストール

# Rustツールチェーン(未インストールの場合)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# GTK4開発ライブラリ

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

# Ubuntu/Debian
sudo apt install libgtk-4-dev libgirepository1.0-dev

# Arch Linux
sudo pacman -S gtk4 gobject-introspection

プロジェクト作成

# 新しいRustプロジェクト作成
cargo new my-blinkgtk-browser
cd my-blinkgtk-browser

Cargo.toml設定

[package]
name = "my-blinkgtk-browser"
version = "0.1.0"
edition = "2021"

[dependencies]
gtk4 = "0.9"
glib = "0.20"
gio = "0.20"

[build-dependencies]
pkg-config = "0.3"

build.rs作成

// build.rs
use std::env;
use std::path::PathBuf;

fn main() {
    // BlinkGTKライブラリのパスを設定
    let blinkgtk_lib = "/path/to/BlinkGTK/lib";

    println!("cargo:rustc-link-search=native={}", blinkgtk_lib);
    println!("cargo:rustc-link-lib=blinkgtk");

    println!("cargo:rerun-if-changed=build.rs");
}

環境変数設定

# LD_LIBRARY_PATHにBlinkGTKライブラリを追加
export LD_LIBRARY_PATH=/path/to/BlinkGTK/lib:$LD_LIBRARY_PATH

# GI_TYPELIB_PATHにBlinkGTK typelibを追加
export GI_TYPELIB_PATH=/path/to/BlinkGTK/gir:$GI_TYPELIB_PATH

最小限のブラウザ

基本的な使い方

// src/main.rs
use gtk4::prelude::*;
use gtk4::{glib, Application, ApplicationWindow};
use std::ffi::CString;

// BlinkGTK FFI宣言
mod ffi {
    use glib::ffi::{gboolean, gchar, gdouble, GType};
    use gtk4::ffi::GtkWidget;

    extern "C" {
        pub fn blink_gtk_init(argc: *mut i32, argv: *mut *mut *mut gchar);
        pub fn blink_gtk_shutdown();
        pub fn blink_web_view_new() -> *mut GtkWidget;
        pub fn blink_web_view_load_uri(web_view: *mut GtkWidget, uri: *const gchar);
    }
}

const APP_ID: &str = "org.example.MinimalBrowser";

fn main() -> glib::ExitCode {
    // BlinkGTK初期化
    unsafe {
        let mut argc = std::env::args().len() as i32;
        let mut argv: Vec<_> = std::env::args()
            .map(|arg| CString::new(arg).unwrap().into_raw())
            .collect();
        let mut argv_ptr = argv.as_mut_ptr();

        ffi::blink_gtk_init(&mut argc, &mut argv_ptr);
    }

    // GTK4アプリケーション作成
    let app = Application::builder().application_id(APP_ID).build();

    app.connect_activate(build_ui);

    let exit_code = app.run();

    // BlinkGTK終了
    unsafe {
        ffi::blink_gtk_shutdown();
    }

    exit_code
}

fn build_ui(app: &Application) {
    // ウィンドウ作成
    let window = ApplicationWindow::builder()
        .application(app)
        .title("BlinkGTK Minimal Browser")
        .default_width(1024)
        .default_height(768)
        .build();

    // BlinkWebView作成
    let web_view_ptr = unsafe { ffi::blink_web_view_new() };
    let web_view = unsafe { gtk4::Widget::from_glib_none(web_view_ptr) };

    // ウィンドウに追加
    window.set_child(Some(&web_view));

    // URLをロード
    let url = "https://www.example.com";
    unsafe {
        let url_c = CString::new(url).unwrap();
        ffi::blink_web_view_load_uri(web_view.as_ptr() as *mut _, url_c.as_ptr());
    }

    // ウィンドウを表示
    window.present();
}

ビルドと実行

# ビルド
cargo build --release

# 実行
LD_LIBRARY_PATH=/path/to/BlinkGTK/lib:$LD_LIBRARY_PATH \
  ./target/release/my-blinkgtk-browser

シグナル処理

BlinkLoadEvent列挙型

#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlinkLoadEvent {
    Started = 0,
    Redirected = 1,
    Committed = 2,
    Finished = 3,
}

impl BlinkLoadEvent {
    pub fn from_i32(value: i32) -> Option<Self> {
        match value {
            0 => Some(BlinkLoadEvent::Started),
            1 => Some(BlinkLoadEvent::Redirected),
            2 => Some(BlinkLoadEvent::Committed),
            3 => Some(BlinkLoadEvent::Finished),
            _ => None,
        }
    }

    pub fn as_str(&self) -> &'static str {
        match self {
            BlinkLoadEvent::Started => "STARTED",
            BlinkLoadEvent::Redirected => "REDIRECTED",
            BlinkLoadEvent::Committed => "COMMITTED",
            BlinkLoadEvent::Finished => "FINISHED",
        }
    }
}

シグナル接続

use gtk4::{glib, prelude::*};

fn build_ui(app: &Application) {
    // ... ウィンドウとWebView作成 ...

    // load-changedシグナル接続
    web_view.connect_closure(
        "load-changed",
        false,
        glib::closure_local!(|_web_view: gtk4::Widget, load_event: i32| {
            if let Some(event) = BlinkLoadEvent::from_i32(load_event) {
                println!("Load state: {}", event.as_str());

                if event == BlinkLoadEvent::Finished {
                    unsafe {
                        let uri_ptr = ffi::blink_web_view_get_uri(
                            _web_view.as_ptr() as *mut _
                        );
                        let title_ptr = ffi::blink_web_view_get_title(
                            _web_view.as_ptr() as *mut _
                        );

                        let uri = if !uri_ptr.is_null() {
                            glib::GString::from_glib_none(uri_ptr).to_string()
                        } else {
                            String::from("(none)")
                        };

                        let title = if !title_ptr.is_null() {
                            glib::GString::from_glib_none(title_ptr).to_string()
                        } else {
                            String::from("(none)")
                        };

                        println!("Page loaded: {} ({})", title, uri);
                    }
                }
            }
        }),
    );

    // title-changedシグナル接続
    web_view.connect_closure(
        "title-changed",
        false,
        glib::closure_local!(|_web_view: gtk4::Widget| {
            unsafe {
                let title_ptr = ffi::blink_web_view_get_title(
                    _web_view.as_ptr() as *mut _
                );
                if !title_ptr.is_null() {
                    let title = glib::GString::from_glib_none(title_ptr).to_string();
                    println!("Title changed: {}", title);
                }
            }
        }),
    );

    // load-failedシグナル接続
    web_view.connect_closure(
        "load-failed",
        false,
        glib::closure_local!(
            |_web_view: gtk4::Widget,
             load_event: i32,
             failing_uri: String,
             error: glib::Error| -> bool {
                if let Some(event) = BlinkLoadEvent::from_i32(load_event) {
                    println!("Load failed: {}", event.as_str());
                    println!("  URI: {}", failing_uri);
                    println!("  Error: {}", error);
                }
                true // Signal handled
            }
        ),
    );
}

プロパティ監視

notify::プロパティ名シグナル

use std::cell::RefCell;
use std::rc::Rc;

fn build_ui(app: &Application) {
    // ... ウィンドウとWebView作成 ...

    let progress_bar = Rc::new(RefCell::new(ProgressBar::new()));
    progress_bar.borrow().set_hexpand(true);
    progress_bar.borrow().set_show_text(true);

    // notify::estimated-load-progress
    {
        let progress = progress_bar.clone();
        let web_view_clone = web_view.clone();

        web_view.connect_notify_local(
            Some("estimated-load-progress"),
            move |_web_view, _pspec| unsafe {
                let prog = ffi::blink_web_view_get_estimated_load_progress(
                    web_view_clone.as_ptr() as *mut _
                );
                println!("Progress: {:.0}%", prog * 100.0);

                progress.borrow().set_fraction(prog);
                progress.borrow().set_text(Some(&format!("{:.0}%", prog * 100.0)));
            },
        );
    }

    // notify::uri
    {
        let web_view_clone = web_view.clone();

        web_view.connect_notify_local(
            Some("uri"),
            move |_web_view, _pspec| unsafe {
                let uri_ptr = ffi::blink_web_view_get_uri(
                    web_view_clone.as_ptr() as *mut _
                );
                if !uri_ptr.is_null() {
                    let uri = glib::GString::from_glib_none(uri_ptr).to_string();
                    println!("URI changed: {}", uri);
                }
            },
        );
    }

    // notify::title
    {
        let window = window.clone();
        let web_view_clone = web_view.clone();

        web_view.connect_notify_local(
            Some("title"),
            move |_web_view, _pspec| unsafe {
                let title_ptr = ffi::blink_web_view_get_title(
                    web_view_clone.as_ptr() as *mut _
                );
                if !title_ptr.is_null() {
                    let title = glib::GString::from_glib_none(title_ptr).to_string();
                    window.set_title(Some(&format!("{} - Browser", title)));
                }
            },
        );
    }
}

ナビゲーション機能

ナビゲーションメソッド

use gtk4::{Box, Button, Entry, Orientation};

fn build_ui(app: &Application) {
    // ... ウィンドウ作成 ...

    let main_box = Box::new(Orientation::Vertical, 0);

    // ツールバー
    let toolbar = Box::new(Orientation::Horizontal, 5);

    // 戻るボタン
    let back_button = Button::with_label("◀");
    {
        let web_view = web_view.clone();
        back_button.connect_clicked(move |_| {
            unsafe {
                ffi::blink_web_view_go_back(web_view.as_ptr() as *mut _);
            }
        });
    }
    toolbar.append(&back_button);

    // 進むボタン
    let forward_button = Button::with_label("▶");
    {
        let web_view = web_view.clone();
        forward_button.connect_clicked(move |_| {
            unsafe {
                ffi::blink_web_view_go_forward(web_view.as_ptr() as *mut _);
            }
        });
    }
    toolbar.append(&forward_button);

    // リロードボタン
    let reload_button = Button::with_label("↻");
    {
        let web_view = web_view.clone();
        reload_button.connect_clicked(move |_| {
            unsafe {
                ffi::blink_web_view_reload(web_view.as_ptr() as *mut _);
            }
        });
    }
    toolbar.append(&reload_button);

    // URL Entry
    let url_entry = Entry::new();
    url_entry.set_hexpand(true);
    {
        let web_view = web_view.clone();
        url_entry.connect_activate(move |entry| {
            let url = entry.text().to_string();
            unsafe {
                let url_c = CString::new(url).unwrap();
                ffi::blink_web_view_load_uri(
                    web_view.as_ptr() as *mut _,
                    url_c.as_ptr()
                );
            }
        });
    }
    toolbar.append(&url_entry);

    main_box.append(&toolbar);
    main_box.append(&web_view);

    window.set_child(Some(&main_box));
}

ナビゲーション状態の確認

use std::cell::RefCell;
use std::rc::Rc;

// ナビゲーションボタン更新関数
let back_button_rc = Rc::new(RefCell::new(back_button.clone()));
let forward_button_rc = Rc::new(RefCell::new(forward_button.clone()));

let update_navigation_buttons = {
    let web_view = web_view.clone();
    let back_btn = back_button_rc.clone();
    let forward_btn = forward_button_rc.clone();

    move || {
        unsafe {
            let can_go_back = ffi::blink_web_view_can_go_back(
                web_view.as_ptr() as *mut _
            ) != 0;
            let can_go_forward = ffi::blink_web_view_can_go_forward(
                web_view.as_ptr() as *mut _
            ) != 0;

            back_btn.borrow().set_sensitive(can_go_back);
            forward_btn.borrow().set_sensitive(can_go_forward);
        }
    }
};

// load-changedで呼び出し
web_view.connect_closure(
    "load-changed",
    false,
    glib::closure_local!(move |_web_view: gtk4::Widget, _load_event: i32| {
        update_navigation_buttons();
    }),
);

実践的な使い方

型安全なラッパークラス

use gtk4::glib;
use std::ffi::CString;

pub struct BlinkWebView {
    widget: gtk4::Widget,
}

impl BlinkWebView {
    pub fn new() -> Self {
        let widget_ptr = unsafe { ffi::blink_web_view_new() };
        let widget = unsafe { gtk4::Widget::from_glib_none(widget_ptr) };

        Self { widget }
    }

    pub fn load_uri(&self, uri: &str) {
        unsafe {
            let uri_c = CString::new(uri).unwrap();
            ffi::blink_web_view_load_uri(
                self.widget.as_ptr() as *mut _,
                uri_c.as_ptr()
            );
        }
    }

    pub fn reload(&self) {
        unsafe {
            ffi::blink_web_view_reload(self.widget.as_ptr() as *mut _);
        }
    }

    pub fn go_back(&self) {
        unsafe {
            ffi::blink_web_view_go_back(self.widget.as_ptr() as *mut _);
        }
    }

    pub fn go_forward(&self) {
        unsafe {
            ffi::blink_web_view_go_forward(self.widget.as_ptr() as *mut _);
        }
    }

    pub fn can_go_back(&self) -> bool {
        unsafe {
            ffi::blink_web_view_can_go_back(self.widget.as_ptr() as *mut _) != 0
        }
    }

    pub fn can_go_forward(&self) -> bool {
        unsafe {
            ffi::blink_web_view_can_go_forward(self.widget.as_ptr() as *mut _) != 0
        }
    }

    pub fn get_uri(&self) -> Option<String> {
        unsafe {
            let uri_ptr = ffi::blink_web_view_get_uri(self.widget.as_ptr() as *mut _);
            if !uri_ptr.is_null() {
                Some(glib::GString::from_glib_none(uri_ptr).to_string())
            } else {
                None
            }
        }
    }

    pub fn get_title(&self) -> Option<String> {
        unsafe {
            let title_ptr = ffi::blink_web_view_get_title(self.widget.as_ptr() as *mut _);
            if !title_ptr.is_null() {
                Some(glib::GString::from_glib_none(title_ptr).to_string())
            } else {
                None
            }
        }
    }

    pub fn as_widget(&self) -> &gtk4::Widget {
        &self.widget
    }
}

使用例

fn build_ui(app: &Application) {
    let window = ApplicationWindow::builder()
        .application(app)
        .title("Modern Browser")
        .build();

    // 型安全なラッパーを使用
    let web_view = BlinkWebView::new();

    web_view.load_uri("https://www.example.com");

    window.set_child(Some(web_view.as_widget()));
    window.present();
}

トラブルシューティング

原因: libblinkgtk.soへのリンクが失敗

解決方法:

# build.rsを確認
# Cargo.tomlに以下を追加
[build-dependencies]
pkg-config = "0.3"

# LD_LIBRARY_PATHを設定
export LD_LIBRARY_PATH=/path/to/BlinkGTK/lib:$LD_LIBRARY_PATH

原因: 複数のソースファイルでFFI宣言が重複

解決方法:

// ffi.rsとして分離
pub mod ffi {
    // FFI宣言をここに集約
}

// 他のファイルから使用
use crate::ffi;

問題: Segmentation fault at runtime

原因: BlinkGTK初期化前にAPIを呼び出している

解決方法:

fn main() -> glib::ExitCode {
    // 必ずblink_gtk_init(&argc, &argv)を最初に呼び出す
    unsafe {
        let mut argc = std::env::args().len() as i32;
        let mut argv: Vec<_> = std::env::args()
            .map(|arg| CString::new(arg).unwrap().into_raw())
            .collect();
        let mut argv_ptr = argv.as_mut_ptr();

        ffi::blink_gtk_init(&mut argc, &mut argv_ptr);
    }

    // GTK4アプリケーション作成
    let app = Application::builder().application_id(APP_ID).build();
    // ...
}

デバッグログの有効化

# Rustのバックトレース有効化
export RUST_BACKTRACE=1

# GTK4のデバッグログ
export G_MESSAGES_DEBUG=all

# 実行
cargo run

サンプルコード

完全なサンプルコードは以下にあります:

ビルド・実行方法(C言語版):

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

まとめ

BlinkGTKをRustから使用することで、以下のメリットがあります:

次のステップ:


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