Rust言語バインディング対応
BlinkGTKはGObject Introspectionに対応しているため、Rustからgtk4-rsを通じて利用できます。Rustの型安全性とゼロコスト抽象化により、安全で高速なブラウザアプリケーションを構築できます。
# 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[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
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#[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
}
),
);
}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) -> >k4::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();
}blink_web_view_new原因: 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_PATHblink_gtk_init原因: 複数のソースファイルでFFI宣言が重複
解決方法:
// ffi.rsとして分離
pub mod ffi {
// FFI宣言をここに集約
}
// 他のファイルから使用
use crate::ffi;原因: 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完全なサンプルコードは以下にあります:
examples/blinkgtk_browser.c -
C言語による最小限のブラウザ(公式サンプル)ビルド・実行方法(C言語版):
cd $BLINKGTK_ROOT
gcc -o blinkgtk_browser examples/blinkgtk_browser.c $(pkg-config --cflags --libs blinkgtk-0.1)
./blinkgtk_browserBlinkGTKをRustから使用することで、以下のメリットがあります:
次のステップ:
BlinkGTK Project | Copyright 2025 | BSD-3-Clause License