native

module
v0.1.5 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT

README ΒΆ

native

test

Small, focused Go packages that bind native OS and hardware APIs without cgo.

Each package fills one gap the standard library leaves open: clipboard, desktop notifications, file-system watching, serial ports, mmap. A clean Go API on top, a per-platform backend underneath. No C toolchain, no bundled native libraries. The backends call what the OS already ships, through purego, syscall, dlopen/LoadLibrary, the Objective-C runtime, COM, and D-Bus.

Sibling of glaze, the pure-Go WebView binding, and it comes from the same bet: you can port genuinely hairy native integration to Go and keep what cgo otherwise takes away. CGO_ENABLED=0 builds, six GOOS/GOARCH targets cross-compiled from one machine, reproducible output, and a go install that works for whoever clones the repo with no compiler set up.

Philosophy

  • Small over sprawling. One package, one job. No "parallel stdlib", no god-package. Each directory here is independently useful and independently testable, but they compose into a real desktop app when used together.
  • Idiomatic Go at the seams. The public API is plain Go: string, []byte, error, small option structs. Native handles (objc.ID, COM vtables, HWND, file descriptors) never leak across the package boundary, so the Win32/Cocoa/portal backend can change without touching callers.
  • Zero cgo, always. CGO_ENABLED=0 builds and cross-compiles for every supported target.
  • Honest about platforms. Where a platform genuinely can't do something cheaply (Linux clipboard ownership, Wayland global hotkeys), the package returns a clear sentinel error and the README says so, rather than pretending.

Non-goals

Deliberately not attempted here. These are maintenance black holes, or already well-served elsewhere:

  • A complete GUI toolkit (use glaze + HTML, or a real toolkit).
  • Full audio engines / DSP, Vulkan/Metal/WebGPU, generic USB stacks.
  • "Perfect" Wayland coverage. macOS and Windows are tractable; Linux desktop is fragmented and gets best-effort, portal-first support.

Packages

Status: βœ… done Β· 🚧 in progress Β· ⬜ planned Β· 🟦 lives in glaze instead (window/app-coupled).

Package What macOS Windows Linux Status
clipboard Read/write text clipboard NSPasteboard Win32 clipboard X11/Wayland 🚧
notify Desktop notifications UNUserNotification WinRT toast D-Bus ⬜
tray System tray / status-bar icon + menu NSStatusItem Shell_NotifyIcon β€” (needs D-Bus) βœ…
keychain Credential / secret storage Keychain Credential Manager / DPAPI Secret Service ⬜
fswatch File-system change notifications FSEvents/kqueue ReadDirectoryChangesW inotify ⬜
serial Serial port I/O termios DCB/CreateFile termios ⬜
filedialog Native open/save file panels NSOpenPanel/NSSavePanel ⬜ ⬜ 🚧
mmap Memory-mapped files syscall.Mmap MapViewOfFile syscall.Mmap βœ…
singleinstance Single-instance lock + arg hand-off flock/socket named pipe flock/socket βœ…
openurl Open URL in browser, reveal file in file manager NSWorkspace ShellExecuteW xdg-open βœ…
power Keep the system awake (inhibit idle sleep) IOKit assertion SetThreadExecutionState β€” (needs D-Bus) βœ…
nocapture Black out a window in screenshots/recordings β€” (Apple removed the API) SetWindowDisplayAffinity β€” (no compositor API) βœ…
Lives in glaze, not here

Features that need the application's window/run loop belong in the desktop framework, glaze:

  • Native application menus: the macOS menu bar, About/Preferences/Quit, window menus on Windows/Linux (shipped as glaze/menu).

File dialogs started out on that list, but standalone panels turned out not to need the host window: they live here as filedialog, and the caller is responsible for invoking them on the main thread.

The standalone packages above are imported by glaze; they don't depend on it.

Conventions

Every package follows the same shape so they're predictable to use and to write:

  • One package per directory, named for the job (clipboard, serial).
  • Platform split by build tags / filenames: foo_darwin.go, foo_windows.go, foo_linux.go β€” or a single foo_unix.go where one POSIX backend covers them all (mmap, singleinstance) β€” plus a foo_other.go fallback stub, so the module always builds for every GOOS.
  • Public API in a tag-free file (foo.go): doc comments, exported types, and sentinel errors live there and delegate to unexported per-platform funcs.
  • Sentinel errors, e.g. var ErrUnsupported = errors.New("clipboard: not supported on this platform"). Where an error would be noise the package documents a zero value instead (filedialog returns "" both on cancel and on an unsupported platform).
  • No native types in signatures. Inputs/outputs are Go values.
  • A package README.md (API table, per-platform status, caveats) and a runnable example in examples/<pkg>/, same module, no extra deps. See clipboard for the shape.
  • golangci-lint clean on every GOOS. The unsafe.Pointer(uintptr) that go vet's unsafeptr flags (common with purego, e.g. a GlobalLock pointer) goes through a ptr(u uintptr) unsafe.Pointer reinterpret helper. Same bits, same ABI, spelled that way only to quiet go vet.
ABI discipline (the part that bites)

cgo's pointer rules still apply conceptually even without cgo. The scars, learned porting glaze's three backends:

  • No Go pointer is ever held by native code. The GC can move Go memory. Pass an integer id, keep a map[id]*T on the Go side, resolve it in the callback.
  • Anything handed to native code must stay alive and unmovable. Keep it in a package-level slice/map (callback trampolines, COM vtables).
  • Struct-by-value is architecture-specific. A 16-byte struct goes by hidden reference on Win64 amd64 but packs into registers on arm64 (AAPCS64), so it needs *_amd64.go / *_arm64.go. This does not show up in cross-compilation; it compiles clean and breaks at runtime.
  • purego.NewCallback on Windows is limited: ≀9 uintptr args, no float / struct-by-value, single uintptr return. Design inbound callbacks within that.
  • Thread affinity. GUI/COM APIs are pinned to a thread (Cocoa β†’ real main thread; GTK β†’ the gtk_init thread; COM apartment + pump β†’ its thread). Use runtime.LockOSThread and route cross-thread calls through a dispatch.

Testing reality

ABI bugs do not appear in cross-compilation. You have to run on the target. CI (GitHub Actions) builds, vets and tests on macOS, Windows and Linux runners, runs make cross to compile-check every GOOS/GOARCH, and runs golangci-lint across each GOOS. Where a package has a backend on the runner its tests run for real (clipboard's round trip on macOS and Windows); where it doesn't, they skip (clipboard on Linux, for now) instead of failing. A file that says // UNTESTED on this host means exactly that: it compiles, but no human or CI on that OS has confirmed it yet.

Hardware is the final word. A real GNOME desktop ships GTK3 and GTK4 side by side; a CI runner carries exactly one stack. That gap hid a gtk_init crash in glaze until it ran on an actual Ubuntu box, so single-stack CI is necessary but not sufficient.

License

MIT. See LICENSE.


More of my projects

  • glaze: WebView desktop apps in Go, cgo-free.
  • minigui: a tiny immediate-mode GUI for Ebitengine.
  • kutta: a 2D wind tunnel; watch air misbehave around an airfoil.
  • neko: the classic desktop cat chasing your pointer, in Go.

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

Directories ΒΆ

Path Synopsis
Package clipboard provides cgo-free access to the system text clipboard.
Package clipboard provides cgo-free access to the system text clipboard.
examples
clipboard command
Command clipboard demonstrates github.com/crgimenes/native/clipboard: it writes text to the system clipboard, reads it back, and restores whatever was there before.
Command clipboard demonstrates github.com/crgimenes/native/clipboard: it writes text to the system clipboard, reads it back, and restores whatever was there before.
filedialog command
Command filedialog demonstrates github.com/crgimenes/native/filedialog: it shows the native open panel, then the native save panel.
Command filedialog demonstrates github.com/crgimenes/native/filedialog: it shows the native open panel, then the native save panel.
mmap command
Command mmap demonstrates github.com/crgimenes/native/mmap: it maps a file, edits it through the byte slice, unmaps, and reads the change back from disk.
Command mmap demonstrates github.com/crgimenes/native/mmap: it maps a file, edits it through the byte slice, unmaps, and reads the change back from disk.
openurl command
Command openurl demonstrates github.com/crgimenes/native/openurl: it opens a web page in the default browser and reveals a file in the file manager.
Command openurl demonstrates github.com/crgimenes/native/openurl: it opens a web page in the default browser and reveals a file in the file manager.
power command
Command power demonstrates github.com/crgimenes/native/power: it asks the OS to keep the system awake, holds the inhibition for a few seconds, then releases it.
Command power demonstrates github.com/crgimenes/native/power: it asks the OS to keep the system awake, holds the inhibition for a few seconds, then releases it.
singleinstance command
Command singleinstance demonstrates github.com/crgimenes/native/singleinstance.
Command singleinstance demonstrates github.com/crgimenes/native/singleinstance.
tray command
Command tray demonstrates github.com/crgimenes/native/tray: it puts an icon with a small menu in the system tray / menu bar and logs when items are chosen.
Command tray demonstrates github.com/crgimenes/native/tray: it puts an icon with a small menu in the system tray / menu bar and logs when items are chosen.
Package filedialog shows the operating system's native open and save file panels, cgo-free.
Package filedialog shows the operating system's native open and save file panels, cgo-free.
Package mmap memory-maps files, cgo-free.
Package mmap memory-maps files, cgo-free.
Package nocapture keeps a window's content out of screenshots, screen recordings and screen sharing, cgo-free β€” captures show the window as a black rectangle while the user keeps seeing it normally, the way DRM-locked video players appear in a shared screen.
Package nocapture keeps a window's content out of screenshots, screen recordings and screen sharing, cgo-free β€” captures show the window as a black rectangle while the user keeps seeing it normally, the way DRM-locked video players appear in a shared screen.
Package openurl opens URLs in the user's default handler and reveals files in the platform file manager, cgo-free.
Package openurl opens URLs in the user's default handler and reveals files in the platform file manager, cgo-free.
Package power provides cgo-free control over system power behavior.
Package power provides cgo-free control over system power behavior.
Package singleinstance enforces a single running instance of an application and hands the launch arguments of later starts to the one already running.
Package singleinstance enforces a single running instance of an application and hands the launch arguments of later starts to the one already running.
Package tray puts an icon with a menu in the system tray / menu bar, cgo-free.
Package tray puts an icon with a menu in the system tray / menu bar, cgo-free.

Jump to

Keyboard shortcuts

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