glaze

package module
v0.0.13 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 14, 2026 License: MIT Imports: 19 Imported by: 0

README

Glaze

Glaze is a pure Go desktop WebView binding built on top of webview/webview and purego.

This repository started from the original go-webview project, but it has diverged significantly and is maintained as a separate codebase with its own goals and APIs.

Goals

  • Keep a CGo-free desktop integration layer for Go applications.
  • Provide pragmatic helpers for desktop app workflows.
  • Keep behavior explicit and testable.
  • Stay friendly to multi-module go.work development.

Key Features

  • No CGo
  • Windows, macOS, and Linux support
  • Native library loading with embedded runtime assets
  • JavaScript to Go binding support
  • Utility helpers for desktop app patterns: BindMethods, RenderHTML, AppWindow

Example Screenshot

Desktop example (examples/desktop):

Glaze Desktop Example

Install

go get github.com/crgimenes/glaze@latest

To use embedded native libraries:

import _ "github.com/crgimenes/glaze/embedded"

Quick Start

package main

import (
 "log"

 "github.com/crgimenes/glaze"
 _ "github.com/crgimenes/glaze/embedded"
)

func main() {
 w, err := glaze.New(true)
 if err != nil {
  log.Fatal(err)
 }
 defer w.Destroy()

 w.SetTitle("Glaze")
 w.SetSize(800, 600, glaze.HintNone)
 w.SetHtml("<h1>Hello from Glaze</h1>")
 w.Run()
}

Desktop Helpers

BindMethods

BindMethods is a convenience layer over Bind that exposes all exported methods of a Go value as JavaScript-callable functions.

What it does:

  • Reflects over exported methods on a struct or pointer receiver.
  • Builds JavaScript names using a prefix and snake_case conversion.
    • Example: GetUserByID with prefix api becomes api_get_user_by_id.
  • Applies the same function signature rules as Bind:
    • no return
    • value
    • error
    • value and error
  • Returns the list of bound names so you can log or verify registration.

This is useful when you have a service object and want to expose a consistent JavaScript API without writing one Bind call per method.

type Store struct{}

func (s *Store) GetItems() []string { return []string{"a", "b"} }

bound, err := glaze.BindMethods(w, "store", &Store{})
RenderHTML

RenderHTML renders a named Go html/template into a string for SetHtml.

What it does:

  • Executes a specific template by name (including nested template calls).
  • Returns the final HTML string.
  • Wraps template execution errors with template context.

This is useful when you want server-style template rendering in a local desktop app without running an HTTP server for that page.

html, err := glaze.RenderHTML(tpl, "page", data)
if err != nil {
 return err
}
w.SetHtml(html)
AppWindow

AppWindow wraps an http.Handler inside a native desktop window backed by a local loopback HTTP server.

What it does:

  • Supports selectable transport with platform-aware default:
    • auto (default): unix on macOS/Linux, tcp on Windows
    • tcp: direct loopback HTTP (127.0.0.1)
    • unix: handler served on Unix socket with a lightweight loopback HTTP gateway for browser navigation
  • Starts listeners using random free ports/paths by default (or custom Addr/UnixSocketPath).
  • Creates a native window and navigates it to that local URL.
  • Runs the UI loop and closes the HTTP server when the window exits.
  • Supports window sizing, title, debug mode, and optional readiness callback.
    • OnReady: receives browser URL (always http://127.0.0.1:...).
    • OnReadyInfo: receives resolved backend details (Transport, Backend, Gateway) so you can verify unix vs tcp in logs.

This is the simplest way to reuse an existing net/http application as a desktop app with minimal changes to your routing, templates, and assets.

err := glaze.AppWindow(glaze.AppOptions{
 Title:     "My App",
 Width:     1280,
 Height:    800,
 Transport: glaze.AppTransportAuto,
 Handler:   mux,
 OnReadyInfo: func(info glaze.AppReadyInfo) {
  log.Printf("transport=%s backend=%s gateway=%s", info.Transport, info.Backend, info.Gateway)
 },
})

Running Examples

From the repository root:

go run ./examples/simple
go run ./examples/bind
go run ./examples/zero_tcp

From each example directory:

cd examples/appwindow && go run .
cd examples/desktop && go run .
cd examples/filorepl && go run .

examples/zero_tcp demonstrates a local-first UI with SetHtml + BindMethods only. It does not start an HTTP server, so there is no loopback TCP gateway.

Testing

Default tests (headless safe):

go test ./...

GUI integration test:

go test -tags=integration -run TestWebview ./...

Building on Windows

Use windowsgui to hide the console window:

go build -ldflags="-H windowsgui" .

Project Layout

  • webview.go - core API and binding internals
  • appwindow.go - desktop window plus local HTTP server helper
  • helpers.go - utility helpers (BindMethods, RenderHTML)
  • embedded/ - embedded native library assets per platform
  • examples/ - runnable sample applications

Security: Library Integrity Verification

Glaze embeds native libraries and extracts them to disk before loading. By default, the extraction target is a temporary directory that may be writable by other processes, which could allow an attacker to replace the library file.

To mitigate this, Glaze computes a BLAKE2b-256 hash of the embedded library bytes at runtime and verifies every extracted (or pre-existing) file against that hash before loading. If the hash does not match, extraction fails with an integrity error and the library is not loaded.

Additionally, extracted files are created with restricted permissions (0500 owner read+execute) inside a directory with 0700 permissions.

Custom Library Directory

For production deployments, use ExtractTo to place the library in a secure, application-controlled directory instead of the system temp directory:

package main

import (
 "log"

 "github.com/crgimenes/glaze"
 "github.com/crgimenes/glaze/embedded"
)

func main() {
 // Extract to a directory with restricted access.
 if err := embedded.ExtractTo("/opt/myapp/lib"); err != nil {
  log.Fatal(err)
 }

 w, err := glaze.New(true)
 if err != nil {
  log.Fatal(err)
 }
 defer w.Destroy()

 w.SetTitle("Secure App")
 w.SetSize(800, 600, glaze.HintNone)
 w.SetHtml("<h1>Hello</h1>")
 w.Run()
}

When using ExtractTo, do not use import _ "github.com/crgimenes/glaze/embedded" — that blank import triggers init() which extracts to the default temp directory. Call ExtractTo explicitly instead.

Acknowledgments

Documentation

Index

Constants

This section is empty.

Variables

View Source
var VerifyBeforeLoad func(path string) error

VerifyBeforeLoad, when non-nil, is called with the resolved library path immediately before the native library is opened via dlopen/LoadLibrary. The embedded package sets this to a BLAKE2b-256 integrity check so that libraries replaced on disk after extraction are detected before loading.

Functions

func AppWindow

func AppWindow(opts AppOptions) error

AppWindow creates a native window backed by a local HTTP server.

It starts the server on a random loopback port (or the address specified in opts.Addr), opens a webview pointing to it, and runs the UI event loop. When the user closes the window, the server is shut down and AppWindow returns.

This is the recommended way to wrap a full devengine application as a desktop app — pass the configured http.ServeMux as opts.Handler and everything (templates, assets, routes) works unmodified.

func BindMethods

func BindMethods(w WebView, prefix string, obj any) ([]string, error)

BindMethods binds all exported methods of obj as JavaScript functions. Each method is exposed as window.{prefix}_{MethodName}(args...). Methods must follow the same signature rules as Bind:

  • Return either nothing, a value, an error, or (value, error).

Returns the list of bound function names and the first error encountered.

func Init added in v0.0.8

func Init() error

Init prepares the glaze runtime: loads the native webview library and resolves all required symbols. It is safe to call multiple times; only the first call has effect. New and NewWindow call Init automatically, but callers may invoke it earlier to fail fast (e.g. verify that the native library is available before building the rest of the UI).

func RenderHTML

func RenderHTML(tpl *template.Template, name string, data any) (string, error)

RenderHTML executes a named template to a string, suitable for SetHtml(). This allows reusing Go html/template definitions without an HTTP server.

Types

type AppOptions

type AppOptions struct {
	// Title is the window title.
	Title string

	// Width and Height set the initial window dimensions.
	Width  int
	Height int

	// Hint controls window resize behaviour (HintNone, HintMin, HintMax, HintFixed).
	Hint Hint

	// Debug enables the browser developer tools.
	Debug bool

	// Transport selects the backend transport.
	// Defaults to AppTransportAuto.
	Transport AppTransport

	// Addr is the listen address for the local HTTP server.
	// Used by AppTransportTCP and defaults to "127.0.0.1:0".
	Addr string

	// UnixSocketPath is an optional socket path used when Transport is unix.
	// If empty, a temporary socket path is generated automatically.
	UnixSocketPath string

	// Handler is the HTTP handler to serve (typically an http.ServeMux).
	Handler http.Handler

	// OnReady is called once listeners are up, with the navigable base URL.
	// Use it to log the address or perform additional setup.
	OnReady func(addr string)

	// OnReadyInfo is called once listeners are up, with transport details.
	// This is useful to inspect whether backend transport is tcp or unix.
	OnReadyInfo func(info AppReadyInfo)
}

AppOptions configures an AppWindow.

type AppReadyInfo added in v0.0.6

type AppReadyInfo struct {
	// URL is the navigable URL used by the embedded browser.
	URL string

	// Transport is the resolved backend transport in use.
	Transport AppTransport

	// Backend is the backend listener endpoint.
	// - tcp: "ip:port"
	// - unix: "/path/to/socket"
	Backend string

	// Gateway is the loopback gateway endpoint when unix transport is used.
	// For tcp transport this matches Backend.
	Gateway string
}

AppReadyInfo contains transport details once AppWindow listeners are ready.

type AppTransport added in v0.0.6

type AppTransport string

AppTransport selects how AppWindow serves HTTP to the embedded browser.

const (
	// AppTransportAuto chooses the recommended platform default.
	// - macOS/Linux: unix backend socket with loopback HTTP gateway.
	// - Windows: loopback TCP.
	AppTransportAuto AppTransport = "auto"

	// AppTransportTCP serves directly over loopback TCP.
	AppTransportTCP AppTransport = "tcp"

	// AppTransportUnix serves the application handler over a Unix domain socket.
	// A lightweight loopback HTTP gateway is created so the embedded browser can
	// still navigate with a standard http:// URL.
	AppTransportUnix AppTransport = "unix"
)

type Hint

type Hint int

Hints are used to configure window sizing and resizing.

const (
	// Width and height are default size.
	HintNone Hint = iota

	// Width and height are minimum bounds.
	HintMin

	// Width and height are maximum bounds.
	HintMax

	// Window size can not be changed by a user.
	HintFixed
)

type WebView

type WebView interface {
	// Run runs the main loop until it's terminated. After this function exits -
	// you must destroy the webview.
	Run()

	// Terminate stops the main loop. It is safe to call this function from
	// a background thread.
	Terminate()

	// Dispatch posts a function to be executed on the main thread. You normally
	// do not need to call this function, unless you want to tweak the native
	// window.
	Dispatch(f func())

	// Destroy destroys a webview and closes the native window.
	Destroy()

	// Window returns a native window handle pointer. When using GTK backend the
	// pointer is GtkWindow pointer, when using Cocoa backend the pointer is
	// NSWindow pointer, when using Win32 backend the pointer is HWND pointer.
	Window() unsafe.Pointer

	// SetTitle updates the title of the native window. Must be called from the UI
	// thread.
	SetTitle(title string)

	// SetSize updates native window size. See Hint constants.
	SetSize(w, h int, hint Hint)

	// Navigate navigates webview to the given URL. URL may be a properly encoded data.
	// URI. Examples:
	// w.Navigate("https://github.com/webview/webview")
	// w.Navigate("data:text/html,%3Ch1%3EHello%3C%2Fh1%3E")
	// w.Navigate("data:text/html;base64,PGgxPkhlbGxvPC9oMT4=")
	Navigate(url string)

	// SetHtml sets the webview HTML directly.
	// Example: w.SetHtml(w, "<h1>Hello</h1>");
	SetHtml(html string)

	// Init injects JavaScript code at the initialization of the new page. Every
	// time the webview will open a the new page - this initialization code will
	// be executed. It is guaranteed that code is executed before window.onload.
	Init(js string)

	// Eval evaluates arbitrary JavaScript code. Evaluation happens asynchronously,
	// also the result of the expression is ignored. Use RPC bindings if you want
	// to receive notifications about the results of the evaluation.
	Eval(js string)

	// Bind binds a callback function so that it will appear under the given name
	// as a global JavaScript function. Internally it uses webview_init().
	// Callback receives a request string and a user-provided argument pointer.
	// Request string is a JSON array of all the arguments passed to the
	// JavaScript function.
	//
	// f must be a function
	// f must return either value and error or just error
	Bind(name string, f any) error

	// Removes a callback that was previously set by Bind.
	Unbind(name string) error
}

func New

func New(debug bool) (WebView, error)

New calls NewWindow to create a new window and a new webview instance. If debug is non-zero - developer tools will be enabled (if the platform supports them).

func NewWindow

func NewWindow(debug bool, window unsafe.Pointer) (WebView, error)

NewWindow creates a new webview instance. If debug is non-zero - developer tools will be enabled (if the platform supports them). Window parameter can be a pointer to the native window handle. If it's non-null - then child WebView is embedded into the given parent window. Otherwise a new window is created. Depending on the platform, a GtkWindow, NSWindow or HWND pointer can be passed here.

Directories

Path Synopsis
examples
bind command
simple command
zero_tcp command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL