Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/e2e.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/hypatia-scan.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions .github/workflows/static-analysis-gate.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions bindings/rust/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
fn main() {

Check failure on line 1 in bindings/rust/build.rs

View workflow job for this annotation

GitHub Actions / Hypatia neurosymbolic scan

[hypatia] null
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
let lib_path = std::path::Path::new(&manifest_dir).join("../../src/interface/ffi/zig-out/lib");

// Tell cargo to look for libgossamer in the ffi build output directory
println!("cargo:rustc-link-search=native={}", lib_path.display());
println!("cargo:rustc-link-lib=gossamer");

// Also link system dependencies required by WebKitGTK (via gossamer)
println!("cargo:rustc-link-lib=gtk-3");
println!("cargo:rustc-link-lib=gdk-3");
println!("cargo:rustc-link-lib=webkit2gtk-4.1");
}
109 changes: 105 additions & 4 deletions bindings/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: PMPL-1.0-or-later

Check failure on line 1 in bindings/rust/src/lib.rs

View workflow job for this annotation

GitHub Actions / Hypatia neurosymbolic scan

[hypatia] null
// Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
//
//! # gossamer-rs — Rust bindings for the Gossamer webview shell
Expand Down Expand Up @@ -41,6 +41,7 @@
// =============================================================================

#[link(name = "gossamer")]
#[allow(dead_code)]
extern "C" {
fn gossamer_create(
title: *const c_char,
Expand All @@ -51,11 +52,31 @@
fullscreen: u8,
) -> u64;

fn gossamer_create_ex(
title: *const c_char,
width: u32,
height: u32,
min_width: u32,
min_height: u32,
max_width: u32,
max_height: u32,
resizable: u8,
decorations: u8,
fullscreen: u8,
visible: u8,
) -> u64;

fn gossamer_load_html(handle: u64, html: *const c_char) -> c_int;
fn gossamer_navigate(handle: u64, url: *const c_char) -> c_int;
fn gossamer_eval(handle: u64, js: *const c_char) -> c_int;
fn gossamer_set_title(handle: u64, title: *const c_char) -> c_int;
fn gossamer_resize(handle: u64, width: u32, height: u32) -> c_int;
fn gossamer_show(handle: u64) -> c_int;
fn gossamer_hide(handle: u64) -> c_int;
fn gossamer_minimize(handle: u64) -> c_int;
fn gossamer_maximize(handle: u64) -> c_int;
fn gossamer_restore(handle: u64) -> c_int;
fn gossamer_request_close(handle: u64) -> c_int;
fn gossamer_run(handle: u64);
fn gossamer_destroy(handle: u64);

Expand All @@ -82,7 +103,9 @@
fn gossamer_version() -> *const c_char;
fn gossamer_last_error() -> *const c_char;

#[allow(dead_code)]
fn gossamer_tray_create(tooltip: *const c_char) -> u64;
#[allow(dead_code)]
fn gossamer_tray_add_menu_item(
tray: u64,
label: *const c_char,
Expand Down Expand Up @@ -276,9 +299,14 @@
pub title: String,
pub width: u32,
pub height: u32,
pub min_width: Option<u32>,
pub min_height: Option<u32>,
pub max_width: Option<u32>,
pub max_height: Option<u32>,
pub resizable: bool,
pub decorations: bool,
pub fullscreen: bool,
pub visible: bool,
}

impl Default for WindowConfig {
Expand All @@ -287,9 +315,14 @@
title: "Gossamer App".to_string(),
width: 800,
height: 600,
min_width: None,
min_height: None,
max_width: None,
max_height: None,
resizable: true,
decorations: true,
fullscreen: false,
visible: true,
}
}
}
Expand Down Expand Up @@ -323,20 +356,33 @@
})
}

/// Get the raw Gossamer window handle.
///
/// This is useful for compatibility layers that need to call lower-level
/// FFI helpers such as event emission or tray/window controls.
pub fn raw_handle(&self) -> u64 {
self.handle
}

/// Create a new Gossamer application with full window configuration.
pub fn with_config(config: WindowConfig) -> Result<Self, Error> {
let title =
CString::new(config.title.clone()).map_err(|e| Error::InvalidString(e.to_string()))?;

// SAFETY: FFI call to create a webview window
let handle = unsafe {
gossamer_create(
gossamer_create_ex(
title.as_ptr(),
config.width,
config.height,
config.min_width.unwrap_or(0),
config.min_height.unwrap_or(0),
config.max_width.unwrap_or(0),
config.max_height.unwrap_or(0),
config.resizable as u8,
config.decorations as u8,
config.fullscreen as u8,
config.visible as u8,
)
};

Expand Down Expand Up @@ -370,11 +416,16 @@
/// # Example
///
/// ```rust,no_run
/// # use gossamer_rs::{App, Error};
/// # fn main() -> Result<(), Error> {
/// let mut app = App::new("Example", 800, 600)?;
/// app.command("load_document", |payload| {
/// let path = payload["path"].as_str().ok_or("missing path")?;
/// let content = std::fs::read_to_string(path).map_err(|e| e.to_string())?;
/// Ok(serde_json::json!({ "content": content }))
/// });
/// # Ok(())
/// # }
/// ```
pub fn command<F>(&mut self, name: &str, handler: F)
where
Expand Down Expand Up @@ -416,12 +467,15 @@
/// # Example
///
/// ```rust,no_run
/// # use gossamer_rs::{App, Error};
/// # fn main() -> Result<(), Error> {
/// let mut app = App::new("Example", 800, 600)?;
/// app.command_async("fetch_data", |payload| {
/// let url = payload["url"].as_str().ok_or("missing url")?;
/// let body = ureq::get(url).call().map_err(|e| e.to_string())?
/// .into_string().map_err(|e| e.to_string())?;
/// Ok(serde_json::json!({ "body": body }))
/// Ok(serde_json::json!({ "requested": url, "status": "queued" }))
/// });
/// # Ok(())
/// # }
/// ```
pub fn command_async<F>(&mut self, name: &str, handler: F)
where
Expand Down Expand Up @@ -489,6 +543,43 @@
check_result(unsafe { gossamer_resize(self.handle, width, height) })
}

/// Show the window.
pub fn show(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_show(self.handle) })
}

/// Hide the window.
pub fn hide(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_hide(self.handle) })
}

/// Minimize the window.
pub fn minimize(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_minimize(self.handle) })
}

/// Maximize the window.
pub fn maximize(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_maximize(self.handle) })
}

/// Restore the window from minimized or maximized state.
pub fn restore(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_restore(self.handle) })
}

/// Request that the window close, while keeping the app handle alive
/// until normal teardown runs.
pub fn request_close(&self) -> Result<(), Error> {
// SAFETY: handle is valid
check_result(unsafe { gossamer_request_close(self.handle) })
}

/// Apply a Content-Security-Policy to the webview.
///
/// Injects a `<meta http-equiv="Content-Security-Policy" content="...">` tag.
Expand All @@ -497,7 +588,12 @@
/// # Example
///
/// ```rust,no_run
/// # use gossamer_rs::{App, Error};
/// # fn main() -> Result<(), Error> {
/// let app = App::new("Example", 800, 600)?;
/// app.set_csp("default-src 'self'; script-src 'self' 'unsafe-inline'")?;
/// # Ok(())
/// # }
/// ```
pub fn set_csp(&self, csp: &str) -> Result<(), Error> {
let csp_c = CString::new(csp).map_err(|e| Error::InvalidString(e.to_string()))?;
Expand All @@ -516,7 +612,12 @@
/// # Example
///
/// ```rust,no_run
/// # use gossamer_rs::{App, Error};
/// # fn main() -> Result<(), Error> {
/// let app = App::new("Example", 800, 600)?;
/// app.emit("file_changed", r#"{"path":"/tmp/foo.txt"}"#)?;
/// # Ok(())
/// # }
/// ```
pub fn emit(&self, event_name: &str, payload_json: &str) -> Result<(), Error> {
let event_c =
Expand Down
Loading
Loading