Skip to content

crgimenes/glaze

Repository files navigation

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

Examples

Desktop Game of Life Starfield
Desktop example preview Game of Life example preview Starfield example preview
Doom Fire Mandelbrot Falling Sand
Doom Fire example preview Mandelbrot example preview Falling Sand example preview
Raycasting Filo REPL
Raycasting example preview Filo REPL example preview

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

About

Glaze: a CGO-free desktop WebView toolkit for Go, with practical helpers for building native app windows from web UIs.

Topics

Resources

License

Stars

Watchers

Forks

Contributors

Languages