glaze

package module
v0.0.46-goleo.1 Latest Latest
Warning

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

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 19 Imported by: 0

README

Glaze

Glaze is a desktop WebView binding for Go. It is a pure-Go port of webview/webview built on purego, keeping CGo out of the picture. Each backend talks to the WebView framework the OS already ships -- WKWebView on macOS, WebKitGTK on Linux, WebView2 on Windows -- so nothing native is bundled.

It started as a fork of go-webview but has diverged enough to live as a separate codebase with its own goals and API.

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

Why no CGo

This is the whole point of the project, and it's the part that's easy to miss. Most native-WebView bindings reach for CGo, which quietly takes back the things that make Go pleasant to ship: cross-compiling suddenly needs a matching C cross-compiler for every target (mingw for Windows, a sysroot for Linux), builds stop being reproducible, and go install only works for people who already have that C toolchain set up.

glaze keeps CGo out entirely - with purego it dlopen/LoadLibrarys the WebView the OS already ships, so there is no C compiler in the loop. What that buys you:

  • Cross-compile to every desktop from one machine -- no C cross-toolchain, just GOOS/GOARCH:

    GOOS=windows GOARCH=amd64 go build   # from a Mac, from Linux, from anywhere
    GOOS=linux   GOARCH=arm64 go build
    GOOS=darwin  GOARCH=arm64 go build
    
  • CGO_ENABLED=0 builds -- reproducible output, and a go get / go install that just works for whoever clones the repo, with no compiler to install first.

One caveat, "self-contained" isn't misread: glaze does not bundle a browser engine - it is not Electron. The binary ships no native library and stays small, but it uses the system WebView at runtime, so the target machine needs that present: WebView2 on Windows (preinstalled on current Windows 10/11), WebKitGTK on Linux (a package), WKWebView on macOS (built in).

What's in the box

  • No CGo
  • Windows, macOS, and Linux
  • Zero bundled native libraries -- binds the OS WebView directly (WKWebView / WebKitGTK / WebView2)
  • JavaScript to Go binding
  • Helpers for common desktop patterns: BindMethods, RenderHTML, AppWindow, a Go↔JS Events bridge
  • A dependency-free code editor component (glaze/editor): line numbers, syntax highlighting, autocompletion, error marks — Filo and SQL definitions included, languages pluggable
  • Native file dialogs (OpenFile/OpenFiles/SaveFile/OpenDirectory) and a reusable native menu bar (glaze/menu)
  • Custom URL schemes: serve embedded assets from a portless, secure-context app:// origin (NewWithOptions)
  • Window control from Go: SetTitle, SetSize, Focus (keyboard focus into the web content) and Raise (front the window and activate the app)
  • NewWindow(debug, window) embeds the WebView into an existing native window (New is the create-a-window shortcut)
  • Plays nicely with go.work multi-module setups

Glaze stays focused on the window and the WebView. OS features that aren't window-bound -- and especially the more platform-specific or less standardized ones, like desktop notifications and the system tray -- live in native, a sibling collection of small, cgo-free packages on the same purego foundation: clipboard, single-instance locks, opening a URL or revealing a file, memory-mapped files, keeping the machine awake, and more. The two don't depend on each other -- an application imports each directly for what it needs; where a platform can't support something cleanly, the native package returns a clear ErrUnsupported instead of shipping something flaky.

Install

go get github.com/crgimenes/glaze@latest

Requirements

Glaze binds the WebView the operating system already provides; there is nothing to bundle, but that runtime must be present:

  • macOS -- nothing extra. The Cocoa/WebKit frameworks ship with the OS.
  • Linux -- a system WebKitGTK, GTK4 or GTK3; glaze detects which at runtime. The exact libraries and how to install or debug them are in Linux shared libraries below.
  • Windows -- the Microsoft Edge WebView2 Runtime (preinstalled on current Windows 10/11; otherwise install the Evergreen Runtime). It is located via the registry, and New returns an error if it is missing. To bundle zero native DLLs, glaze calls the runtime's internal environment-creation export directly instead of shipping WebView2Loader.dll; that export is undocumented and could change in a future Edge runtime (in which case New returns a clear error). See the note on createEnvironment in webview2_windows.go.
Linux shared libraries

Linux is the hard case. Every distro packages WebKitGTK a little differently and glaze can't paper over all of it -- but what it needs is concrete. These are the exact sonames it tries to dlopen at startup. They have to be loadable by the dynamic linker (on the default search path or in the ldconfig cache, or in LD_LIBRARY_PATH) and the same architecture as your binary -- a 64-bit Go build needs 64-bit libraries.

Always loaded:

  • libglib-2.0.so.0
  • libgobject-2.0.so.0

libwebkitgtk-6.0.so.4 decides the stack: if it loads, glaze uses GTK4; otherwise GTK3. It never loads both -- most desktops have GTK3 and GTK4 installed side by side, and pulling both into one process corrupts GTK's type system and crashes gtk_init.

  • GTK4: libgtk-4.so.1, libwebkitgtk-6.0.so.4, libjavascriptcoregtk-6.0.so.1
  • GTK3: libgtk-3.so.0, libwebkit2gtk-4.1.so.0 (or libwebkit2gtk-4.0.so.37), libjavascriptcoregtk-4.1.so.0 (or libjavascriptcoregtk-4.0.so.18)

On the GTK4 stack, the file dialogs additionally load libgio-2.0.so.0 the first time a dialog opens (it ships with GLib, so it is present wherever the libraries above are).

Installing the WebKitGTK package pulls GTK and GLib in as dependencies:

  • Debian / Ubuntu: apt install libwebkit2gtk-4.1-0 (GTK3) or libwebkitgtk-6.0-4 (GTK4)
  • Fedora: dnf install webkit2gtk4.1 or webkitgtk6.0
  • Arch: pacman -S webkit2gtk-4.1 or webkitgtk-6.0
  • Nix / NixOS: these libraries are not on the default loader path, so a bare go run outside a shell that provides them fails to load. Add webkitgtk_4_1 (or webkitgtk_6_0) to your buildInputs / dev shell, or expose them through LD_LIBRARY_PATH or nix-ld.

If New returns webview: none of [...] could be loaded, the linker can't find that soname. See what's actually visible to it:

ldconfig -p | grep -E 'libwebkit(2)?gtk|libjavascriptcoregtk|libgtk-[34]'

wrong ELF class: ELFCLASS32 means the library was found but in the wrong architecture -- a 64-bit binary was pointed at 32-bit libraries (check your LD_LIBRARY_PATH).

The test suite reflects this: the GUI tests skip themselves when none of these libraries can load, so go test ./... stays green on a box without WebKitGTK instead of failing.

Hello world

package main

import (
	"log"

	"github.com/crgimenes/glaze"
)

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()
}

Glaze pins the goroutine that creates the first window to its current OS thread. Keep direct window calls on that goroutine, and use Dispatch to re-enter the UI thread from background work.

Desktop helpers

BindMethods

A convenience layer over Bind that exposes every exported method of a Go value as a JavaScript-callable function.

What it does:

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

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

Renders a named Go html/template to a string you can pass to SetHtml.

What it does:

  • Runs a specific template (nested calls included).
  • Returns the final HTML string.
  • Wraps execution errors with template context.

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

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

What it does:

  • 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 a Unix socket with a lightweight loopback HTTP gateway for browser navigation
  • Starts listeners on random free ports/paths by default (or a custom Addr / UnixSocketPath).
  • Creates a native window and navigates it to that local URL.
  • Runs the UI loop and shuts down the HTTP server when the window exits.
  • Supports window sizing, title, debug mode, and an optional readiness callback.
    • OnReady receives the browser URL (loopback; http://127.0.0.1:..., or http://[::1]:... if you pass an IPv6 Addr).
    • OnReadyInfo receives the resolved backend details (Transport, Backend, Gateway) so you can verify unix vs tcp from logs.

The shortest path from an existing net/http app to a desktop app, with minimal changes to 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)
	},
})
Custom URL schemes

Serve a window's assets from your own app://-style origin -- one that WebKit and WebView2 treat as a secure context -- without opening a TCP port. Hand NewWithOptions a map of scheme name to handler; the handler turns a request into bytes:

//go:embed ui
var uiFS embed.FS

w, err := glaze.NewWithOptions(glaze.Options{
    Debug: true,
    SchemeHandlers: map[string]glaze.SchemeHandler{
        "app": func(req *glaze.SchemeRequest) *glaze.SchemeResponse {
            data, ctype := serve(req.URL) // from your embedded FS, however you like
            if data == nil {
                return nil // a nil response is a 404
            }
            return &glaze.SchemeResponse{Body: data, MIMEType: ctype}
        },
    },
})
w.Navigate("app://home/index.html") // secure origin, no port

Why not just file:// or SetHtml? Because neither is a secure context, and a large part of the web platform is gated behind one:

Approach Port? Secure context?
Loopback http://127.0.0.1:<port> server opens a port yes
file:// / SetHtml no port no -- crypto.subtle is undefined, getUserMedia/geolocation are blocked, localStorage is unreliable, routing is hash-only
Custom scheme (this) no port yes -- localStorage, crypto.subtle, getUserMedia, and path routing all work

Handlers are supplied at construction (not added later) because macOS bakes the scheme handlers into the WKWebViewConfiguration before the WKWebView exists. New/NewWindow delegate to NewWithOptions, so existing code is unaffected.

Each backend uses its own native mechanism: macOS a WKURLSchemeHandler; Linux webkit_web_context_register_uri_scheme marked secure. Windows has no per-scheme secure flag, so the scheme is served over a per-scheme https://<scheme>.localhost virtual host (an https origin is a secure context) and Navigate rewrites <scheme>://… to it -- so your handler and your Navigate URLs use the one scheme:// form on every platform. See examples/scheme.

First click on an inactive window (macOS)

On macOS a click on a window that does not have focus is spent activating the window: it never reaches the page. For a control panel, a dashboard or a player -- anything the user clicks in passing -- that reads as a broken button, and the user ends up clicking twice.

w, err := glaze.NewWithOptions(glaze.Options{AcceptsFirstMouse: true})

It is opt-in, and deliberately so: the AppKit default is what protects destructive interfaces. In a drawing tool, an editor, or any window with a delete button, a click that merely raises the window must not also press whatever happens to sit under the cursor. Leave it off when a stray first click could destroy something.

macOS only; ignored on Linux and Windows, where a click on an inactive window already reaches the content. The mechanism is a WKWebView subclass answering YES to acceptsFirstMouse: -- AppKit asks the view under the cursor, so there is no window-level or runtime switch for it.

It is not always enough. AppKit will deliver the click to the view, but WebKit hosts the page in another process and does not always forward that first click to the DOM while the window is not key -- measured, not assumed. When a program knows it took its own focus away (it launched a window that activates, say), the reliable answer is to take the focus back:

w.Raise() // front the window and activate the app; the next click just works

Raise is the blunt instrument and should be used sparingly -- stealing focus from someone typing in another application is worse than the second click it saves. Focus is the other half: it moves the caret inside the page.

Events

A lightweight publish/subscribe bridge between Go and JavaScript, layered on Bind/Init/Eval with no extra native code. Create one per window, then emit and subscribe on either side; an event reaches every listener on both sides exactly once.

ev, err := glaze.NewEvents(w)
if err != nil {
	log.Fatal(err)
}

// Go subscribes; each argument arrives as raw JSON to decode as you like.
ev.On("ui:save", func(args ...json.RawMessage) {
	var name string
	_ = json.Unmarshal(args[0], &name)
	log.Println("save requested for", name)
})

// Go emits to JS — safe to call from any goroutine.
_ = ev.Emit("app:ready", map[string]any{"version": 3})
// JS subscribes to Go events and emits its own.
glaze.events.on("app:ready", (info) => console.log("ready", info.version));
glaze.events.emit("ui:save", "untitled.txt");

On returns a function that cancels that one subscription; Off(name) drops all of them. Go handlers run on the goroutine that emitted (or the binding goroutine for events coming from JS), so re-enter the UI thread with Dispatch if a handler touches the window. See examples/events.

File dialogs

Native open/save/directory dialogs, exposed on the WebView interface (a glaze extension; upstream webview has none):

path, _  := w.OpenFile(glaze.FileDialogOptions{
    Title:   "Open an image",
    Filters: []glaze.FileFilter{{Name: "Images", Extensions: []string{"png", "jpg"}}},
})
paths, _ := w.OpenFiles(glaze.FileDialogOptions{})                     // multi-select
saveTo, _ := w.SaveFile(glaze.FileDialogOptions{Filename: "untitled.txt"})
dir, _   := w.OpenDirectory(glaze.FileDialogOptions{})

Backends: NSOpenPanel/NSSavePanel (macOS), IFileOpenDialog/IFileSaveDialog (Windows), GtkFileChooserNative (Linux). Each shows the modal dialog, blocks the calling goroutine, and returns the chosen path(s) or "" on cancel. Call them from Bind callbacks (a background goroutine), never from the UI thread. See examples/filedialog.

Native menus

github.com/crgimenes/glaze/menu installs a native menu bar. It depends only on purego, not on the WebView, so a game or any other window-owning app can use it the same way.

menu.Set([]menu.Item{
    {Title: "App", Submenu: []menu.Item{
        {Title: "About", OnClick: showAbout},
        {Separator: true},
        {Title: "Quit", Shortcut: "cmd+q", OnClick: w.Terminate},
    }},
    {Title: "Edit", Submenu: []menu.Item{
        {Title: "Copy", Shortcut: "cmd+c", OnClick: doCopy},
    }},
}, menu.Options{Window: w.Window()})

macOS (NSMenu) and Windows (Win32 menu bar) are implemented; Linux returns ErrUnsupported. See examples/menu.

Running the examples

examples/ is a separate Go module (it keeps the library's go.mod purego-only), so run the examples from inside it:

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

Or from each example directory:

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

examples/zero_tcp shows a local-first UI with no HTTP server and no loopback TCP gateway: it stages the frontend to disk, navigates to a file:// URL, and talks to Go through BindMethods alone.

examples/scheme is the secure-context counterpart: it serves an embedded frontend from a portless app:// origin via a custom scheme handler, so localStorage, crypto.subtle, and path routing all work (they do not on the file:// origin above).

Testing

go test ./...

This runs the pure-logic unit tests (binding marshalling, transport selection) plus the per-platform GUI smoke tests, which drive a real WebView (WKWebView / WebKitGTK / WebView2). Those GUI tests skip themselves when the system WebView can't run here -- no display, or the libraries aren't installed (WebKitGTK on Linux, the Edge WebView2 Runtime on Windows) -- so the command above stays green on a headless or minimal box instead of failing.

For a fast, headless run, -short skips the GUI scenarios on every platform (each drives a real run loop and can take a few seconds):

go test -short ./...

To actually exercise the GUI tests on Linux, install WebKitGTK and run under a virtual display:

xvfb-run -a go test ./...

Building on Windows

Use windowsgui to hide the console window:

go build -ldflags="-H windowsgui" .

Project layout

  • webview_common.go -- the WebView interface, function-wrapper, and JS marshalling
  • webview_bridge.go / webview_bridge_webkit.go -- the injected JS bridge (init/bind scripts)
  • webview_darwin.go / webview_linux.go / webview_windows.go (+ webview2_windows.go, putbounds_amd64.go, putbounds_arm64.go) -- the per-OS pure-Go backends
  • scheme.go (+ webview2_scheme_windows.go) -- the custom URL-scheme handler API (NewWithOptions / SchemeHandler)
  • appwindow.go -- desktop window + local HTTP server helper
  • dialog.go / dialog_darwin.go / dialog_windows.go / dialog_linux.go -- native file dialogs
  • helpers.go -- utility helpers (BindMethods, RenderHTML)
  • events.go -- the Go↔JS publish/subscribe events bridge (NewEvents)
  • menu/ -- the standalone native menu-bar package (github.com/crgimenes/glaze/menu)
  • examples/ -- runnable sample applications (their own Go module)

Glaze loads the OS WebView framework directly and bundles or extracts no native library, so there is no extracted file to verify or swap.

Acknowledgments


More of my projects

  • filo: a small scripting language safe to embed in Go programs.
  • keikiban: a PostgreSQL dashboard; database load, top SQL, locks, index health.
  • kutta: a 2D wind tunnel; watch air misbehave around an airfoil.
  • minigui: a tiny immediate-mode GUI for Ebitengine.
  • neko: the classic desktop cat chasing your pointer, in Go.

More at github.com/crgimenes and crg.eti.br.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrIconUnsupported = errors.New("glaze: setting the application icon at runtime is not supported on this platform")

ErrIconUnsupported is returned where the platform has no application-level icon to set at runtime — on Windows and Linux the icon comes from the executable's resources or the desktop entry, both decided before the process exists.

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

func Init() error

Init loads the system GTK + WebKitGTK libraries and resolves all symbols. Safe to call multiple times; New calls it.

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.

func SetAppIcon

func SetAppIcon(png []byte) error

SetAppIcon gives the running application the icon in png — the picture the Dock, the taskbar or the switcher shows for the PROCESS, which is a different thing from the icon baked into the executable file.

It is application-wide, not per window, because that is what every platform that has the concept models: a process has one face. It is also why this is a package function rather than a WebView method — a program with no webview at all (a game window, a headless helper that raises a dialog) wants it just as much.

Where a platform has no such concept, it reports ErrIconUnsupported, which callers are expected to log and carry on: an application that refuses to start because it could not wear its own icon is worse than a plain one.

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

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

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 EventHandler

type EventHandler func(args ...json.RawMessage)

EventHandler receives the event's arguments, each as the raw JSON the emitter sent, to unmarshal into whatever type the handler expects.

type Events

type Events struct {
	// contains filtered or unexported fields
}

Events is a lightweight publish/subscribe bridge between Go and JavaScript, layered entirely on the public Bind/Init/Eval primitives (no extra native code). Create one per WebView with NewEvents, then Emit and subscribe on either side: an event reaches every listener on both sides exactly once, and neither side echoes back to create a loop.

The matching JavaScript API is installed on the page as window.glaze.events:

glaze.events.on("app:ready", (info) => { ... });
glaze.events.emit("ui:save", "untitled.txt");

Events is safe for concurrent use.

func NewEvents

func NewEvents(w WebView) (*Events, error)

NewEvents installs the events bridge on w and returns the handle used to emit and subscribe from Go. Call it once per WebView, before Run. The error is non-nil only if the underlying Bind fails.

func (*Events) Emit

func (e *Events) Emit(name string, data ...any) error

Emit publishes an event to every listener on both sides. Each value in data becomes one argument delivered to the handlers (Go handlers receive it as raw JSON, JS handlers as a decoded value). It is safe to call from any goroutine; the JS-side listeners are notified on the UI thread. Emit returns an error only if a value in data cannot be JSON-encoded, in which case nothing is published.

func (*Events) Off

func (e *Events) Off(name string)

Off removes every handler subscribed to the named event.

func (*Events) On

func (e *Events) On(name string, handler EventHandler) (cancel func())

On subscribes handler to the named event and returns a function that cancels just this subscription. Handlers for a JS-originated event run on the binding goroutine; handlers for a Go-originated event run on the goroutine that called Emit. Re-enter the UI thread with Dispatch if a handler touches the window.

type FileDialogOptions

type FileDialogOptions struct {
	// Title overrides the dialog's title.
	Title string

	// Directory is the initial directory the dialog displays, as a filesystem
	// path. Empty uses the platform default (usually the last-used directory).
	Directory string

	// Filename is the suggested file name. It is used by SaveFile and ignored
	// by the open and directory dialogs.
	Filename string

	// Filters limits the selectable file types. An empty list shows all files.
	// Filters are advisory: a platform may present them differently or let the
	// user override them.
	Filters []FileFilter
}

FileDialogOptions configures a native file dialog. The zero value is valid: it shows a default dialog rooted at the platform's default directory with no type filtering.

type FileFilter

type FileFilter struct {
	// Name is the human-readable label for this filter (e.g. "Images").
	Name string

	// Extensions lists the file extensions WITHOUT the leading dot
	// (e.g. {"png", "jpg"}). An empty list, or an entry "*", matches any file.
	Extensions []string
}

FileFilter restricts a file dialog to files of a given kind.

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 Options

type Options struct {
	// Debug enables the platform web inspector / dev tools.
	Debug bool
	// Window, if non-nil, is an existing native window to embed into (a
	// GtkWindow* / NSWindow* / HWND), mirroring NewWindow.
	Window unsafe.Pointer
	// SchemeHandlers maps a scheme name (without "://", e.g. "app") to its
	// handler, registered as a secure context. Handlers must be installed before
	// the web view is created, so they cannot be added later.
	SchemeHandlers map[string]SchemeHandler

	// AcceptsFirstMouse makes a click on an INACTIVE window reach the page
	// instead of only bringing the window forward.
	//
	// macOS only; ignored elsewhere, where a click on an inactive window already
	// reaches the content. AppKit's default is the opposite of what most web UIs
	// want: the first click is swallowed as activation, so a user who clicks a
	// button in a window that lost focus has to click twice — and the first
	// click looks broken. Turn this on for control panels, dashboards, players
	// and anything else the user clicks in passing.
	//
	// It is OPT-IN because the default protects destructive interfaces: in a
	// drawing tool, an editor, or any window with a delete button, a click that
	// merely raises the window must NOT also press what happens to be under the
	// cursor. Leave it off when a stray first click could destroy something.
	AcceptsFirstMouse bool
}

Options configures a web view created with NewWithOptions.

type SchemeHandler

type SchemeHandler func(*SchemeRequest) *SchemeResponse

SchemeHandler serves responses for one registered scheme. It runs on the UI thread, so keep it fast (serve from an in-memory FS).

type SchemeRequest

type SchemeRequest struct {
	// URL is the full request URL, e.g. "app://host/index.html".
	URL string
}

SchemeRequest describes an incoming request for a registered custom scheme.

type SchemeResponse

type SchemeResponse struct {
	// Body is the response payload. The backend copies or streams it before the
	// handler returns, so it need not outlive the call.
	Body []byte
	// MIMEType defaults to "application/octet-stream" when empty.
	MIMEType string
}

SchemeResponse is what a SchemeHandler returns for a request. A nil response is treated as "not found".

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("<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)

	// Focus moves keyboard focus into the web content, so typing - and a screen
	// reader's cursor - lands inside the page without the user having to click it
	// first. Each platform already does this when its window first appears and
	// when the window is re-activated; Focus is the explicit, on-demand version
	// for pulling focus back into the page. Call it from the UI thread.
	Focus()

	// Raise brings the window to the FRONT and gives the application focus, so
	// it is clickable again without the user having to click twice. This is the
	// case Focus does not cover: Focus moves the caret inside the page, Raise
	// moves the window in front of everything else.
	//
	// The case it exists for: a program that took focus away from itself —
	// launching a child window that activates, finishing a job that raised
	// something else — and now needs its own window usable again. On macOS that
	// matters more than it sounds, because a click on an inactive window is
	// spent activating it (see Options.AcceptsFirstMouse), and for web content
	// even that opt-in is not always enough.
	//
	// It is deliberately blunt, which is also why it should be used sparingly:
	// stealing focus from someone typing in another application is worse than
	// the second click it saves. Call it from the UI thread.
	Raise()

	// 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

	// OpenFile shows an "open file" dialog and returns the chosen path, or "" if
	// the user cancelled.
	OpenFile(opts FileDialogOptions) (string, error)

	// OpenFiles shows an "open file" dialog that allows selecting multiple files
	// and returns the chosen paths, or nil if the user cancelled.
	OpenFiles(opts FileDialogOptions) ([]string, error)

	// SaveFile shows a "save file" dialog and returns the chosen path, or "" if
	// the user cancelled.
	SaveFile(opts FileDialogOptions) (string, error)

	// OpenDirectory shows a directory chooser and returns the chosen directory
	// path, or "" if the user cancelled.
	OpenDirectory(opts FileDialogOptions) (string, error)
}

WebView is the cross-platform handle returned by New and NewWindow. Its methods drive the native window and the embedded web view. Unless a method's own documentation says otherwise, call them from the UI thread (the goroutine that created the first window), and use Dispatch to re-enter that thread from background goroutines.

func New

func New(debug bool) (WebView, error)

New creates a new window and a web view.

func NewWindow

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

NewWindow creates a web view. If window is non-nil it must point to an existing GtkWindow to embed into; otherwise a new window is created.

The first successful call pins the calling goroutine to its OS thread.

func NewWithOptions

func NewWithOptions(opts Options) (WebView, error)

NewWithOptions creates a web view configured by opts, including any custom SchemeHandlers (registered on the web view's context and marked as secure).

Directories

Path Synopsis
Package editor ships a small, dependency-free code editor for glaze WebViews: line numbers, syntax highlighting, autocompletion, error marks and bracket matching, in plain embedded HTML/CSS/JS — no CDN, no framework, nothing fetched at runtime, so a single-binary app stays a single binary.
Package editor ships a small, dependency-free code editor for glaze WebViews: line numbers, syntax highlighting, autocompletion, error marks and bracket matching, in plain embedded HTML/CSS/JS — no CDN, no framework, nothing fetched at runtime, so a single-binary app stays a single binary.
Package menu builds native application menus without cgo.
Package menu builds native application menus without cgo.

Jump to

Keyboard shortcuts

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