arc

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: MIT Imports: 6 Imported by: 0

README

arc

A lightweight framework for building cross-platform desktop applications using Go and web technologies.

Node.js has Electron. Go has Arc.

Write your UI in HTML, CSS, and JavaScript. Write your application logic in Go. Connected by a clean, fast IPC layer — backed by the system's native webview on every platform.

go get github.com/carbon-os/arc
package main

import (
    "github.com/carbon-os/arc"
    "github.com/carbon-os/arc/ipc"
    "github.com/carbon-os/arc/window"
)

func main() {
    app := arc.NewApp(arc.AppConfig{
        Title: "My App",
    })

    app.OnReady(func() {
        win := app.NewBrowserWindow(window.Config{
            Width:  1280,
            Height: 800,
        })

        ipcMain := win.IPC()

        ipcMain.On("ping", func(msg ipc.Message) {
            ipcMain.Send("pong", "hello back!")
        })

        win.OnReady(func() {
            win.LoadFile("frontend/index.html")
        })
    })

    app.Run()
}
// renderer
ipc.post("ping", "hello")
ipc.on("pong", (msg) => console.log(msg))

Why Arc

Most Go desktop frameworks embed a webview in the same process as your application logic. Arc takes a different approach.

Your Go application and each webview renderer run as separate OS processes, connected by Arc's IPC protocol — the same architectural pattern that powers Electron, applied to native webviews with none of the weight.

Your Go app  ──── Arc IPC ────  Native webview renderer
  (main)                           (child process)

Each BrowserWindow owns exactly one renderer process. Multiple windows means multiple independent processes — no shared state, no cross-window interference.

No bundled browser. No CGo in your application code. No GCC requirement. Just Go, a thin native renderer, and the web stack you already know.


Platforms

Platform Engine Minimum Version
Linux WebKit2GTK 4.1 GTK 3, GLib 2.56
macOS WKWebView macOS 11
Windows WebView2 Windows 10 1903+

Getting Started

go get github.com/carbon-os/arc
go run .

API

App
app := arc.NewApp(arc.AppConfig{
    Title:  "My App",
    WebApp: false,       // enable web mode (default: false)
    Host:   "localhost", // web mode host (default: localhost)
    Port:   8080,        // web mode port (default: 8080)
})

app.OnReady(func() {
    // fired once the app is ready to create windows
})

app.OnClose(func() bool {
    return true // true → allow exit, false → suppress
})

app.Run()  // blocks until all windows are closed

Port and Host are only active when WebApp: true and are silently ignored otherwise.

BrowserWindow
win := app.NewBrowserWindow(window.Config{
    Title:  "My App",
    Width:  1280,
    Height: 800,
    Debug:  false,
})

// OnReady fires once the renderer process is connected and ready to
// receive commands. LoadFile, LoadHTML, and LoadURL must be called
// from here or after.
win.OnReady(func() {
    win.LoadFile("frontend/index.html")
})

win.OnClose(func() bool {
    return true // true → allow close, false → suppress
})

win.Quit() // programmatically close this window

Each NewBrowserWindow call spawns a dedicated renderer process. All windows are fully independent — separate processes, transports, and IPC connections.

Renderer

Arc needs a renderer binary to display your UI. There are three ways to provide one.

Prebuilt — recommended

Set Prebuilt: true in RendererConfig and Arc will automatically fetch the correct renderer for your platform and architecture from GitHub Releases, verify its checksum, and cache it locally.

app := arc.NewApp(arc.AppConfig{
    Title: "My App",
    Renderer: arc.RendererConfig{
        Prebuilt: true,
    },
})
arc: fetching prebuilt renderer v0.1.4 (darwin/arm64)...
arc: verified sha256 checksum
arc: renderer ready

Custom path — bring your own build

app := arc.NewApp(arc.AppConfig{
    Title: "My App",
    Renderer: arc.RendererConfig{
        Path: "/path/to/custom/arc-renderer",
    },
})

Local sidecar — ship it yourself

Place an arc-renderer binary next to your application binary. Arc will find it automatically with no config required.

dist/
├── myapp
└── arc-renderer

Renderer lookup order

1. Renderer.Path     — explicit path, always wins
2. ./arc-renderer    — local sidecar next to the binary
3. Prebuilt: true    — fetch from GitHub releases
4. error             — no renderer found

Compile the renderer yourself

The full C++ renderer source is included in the repository under renderer/. If you want to audit, modify, or self-host it:

git clone https://github.com/carbon-os/arc
cd arc/renderer
cmake -B build
cmake --build build

Requires CMake 3.22+ and a C++20 compiler. See renderer/README.md for platform dependencies.

Load UI
win.LoadHTML("<html>...</html>")     // inline HTML
win.LoadFile("frontend/index.html")  // local file, sibling assets resolve automatically
win.LoadURL("https://example.com")   // external URL
Window
win.SetTitle("new title")
win.SetSize(1920, 1080)
win.Eval("document.body.style.background = 'red'")
IPC — Go → Renderer
ipcMain := win.IPC()

ipcMain.Send("channel", "hello")
ipcMain.SendBytes("channel", []byte{0x01, 0x02, 0x03})
ipc.on("channel", (payload) => {
    if (payload instanceof ArrayBuffer) {
        // binary
    } else {
        // text
    }
})
IPC — Renderer → Go
ipc.post("channel", "hello")

// binary
const buf = new Uint8Array([0x01, 0x02, 0x03]).buffer
ipc.post("channel", buf)
ipcMain := win.IPC()

ipcMain.On("channel", func(msg ipc.Message) {
    if msg.IsText()   { s := msg.Text()  }
    if msg.IsBinary() { b := msg.Bytes() }
})

ipcMain.Off("channel") // unregister
Multi-window

Each window gets its own IPC handle. Handlers are scoped to their window's renderer process — no channel collisions, no ambiguity about which window sent a message.

app.OnReady(func() {
    main := app.NewBrowserWindow(window.Config{Title: "Main", Width: 1280, Height: 800})
    settings := app.NewBrowserWindow(window.Config{Title: "Settings", Width: 600, Height: 400})

    mainIPC := main.IPC()
    settingsIPC := settings.IPC()

    mainIPC.On("open-settings", func(msg ipc.Message) {
        settings.Show()
    })

    settingsIPC.On("save", func(msg ipc.Message) {
        mainIPC.Send("settings-updated", msg.Text())
    })

    main.OnReady(func() { main.LoadFile("frontend/main.html") })
    settings.OnReady(func() { settings.LoadFile("frontend/settings.html") })
})

Web Mode

Arc apps can run as a web server, serving your UI in a browser instead of a native window. This lets the same binary run on a Linux server with no display server — a developer or user can simply open their browser and the app renders there, over HTTP and WebSocket.

Web mode is opt-in. An Arc app with no config is purely a native desktop app — no web surface is exposed, and no surprise behaviour occurs on headless machines.

Enabling Web Mode
app := arc.NewApp(arc.AppConfig{
    Title:  "My App",
    WebApp: true,
    Host:   "localhost", // restrict to loopback (default)
    Port:   8080,
})

Set Host to "0.0.0.0" to expose the app on the network. This is an explicit opt-in — localhost is always the default.

How It Works

When web mode is active, Arc replaces the native webview renderer with an HTTP + WebSocket server. Your frontend assets are served as static files. IPC is transparently bridged over WebSocket — your Go and JS code is unchanged.

Native mode:   Go app ──── IPC (pipe) ────  webview process
Web mode:      Go app ──── IPC (WS)   ────  browser tab
Runtime Flags

AppConfig provides the defaults. Runtime flags override them:

./myapp                  # native window
./myapp --webapp         # force web mode
./myapp --port 9000      # override port
./myapp --host 0.0.0.0   # expose to network

Arc also auto-detects headless environments. If WebApp: true and no display server is found (DISPLAY / WAYLAND_DISPLAY unset), Arc falls back to web mode automatically without requiring an explicit flag.

Behaviour When WebApp is False

If WebApp: false (the default) and --webapp is passed at runtime, Arc exits with a clear message rather than silently doing nothing:

arc: web mode is not enabled for this application.
     set WebApp: true in arc.AppConfig to enable it.
     exiting.
Transport Comparison
Native mode Web mode
Renderer OS native webview Browser tab
IPC transport Unix socket / pipe WebSocket
Asset delivery Local filesystem HTTP static server
win.Eval() Direct to webview Broadcast over WebSocket
Multi-client Multiple windows Single session (one active tab)

Project Structure

myapp/
├── main.go
├── frontend/
│   ├── index.html
│   ├── app.js
│   └── style.css
└── arc.toml

Building

go build -o dist/myapp

Produces a self-contained binary with your frontend embedded.

dist/
├── myapp
└── arc-renderer

The renderer ships as a lightweight sidecar alongside your binary. End users need no compiler, no runtime, and no dependencies installed.

In web mode, arc-renderer is not used — the binary serves the UI directly. It is safe to omit from server deployments.


Repository Structure

github.com/carbon-os/arc/
│
├── renderer/                   # C++ renderer source
│
├── window/                     # BrowserWindow
│   └── window.go
│
├── ipc/                        # IPC (On, Off, Send, SendBytes, Message)
│   └── ipc.go
│
├── runtime/                    # Go process management + IPC transport
│   ├── process.go
│   ├── ipc.go
│   ├── bundle.go
│   └── transport.go
│
├── webapp/                     # Web mode HTTP + WebSocket server
│   ├── server.go
│   └── ws.go
├── go.mod
└── README.md

Comparison

Arc Electron
Native webview
Separate processes
Bundled browser
Binary size ~5mb ~200mb
App language Go JavaScript
Web / server mode
Multi-window

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

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

App is the top-level handle for an Arc application. Create one with NewApp, register callbacks, then call Run.

func NewApp

func NewApp(cfg AppConfig) *App

NewApp creates a new App with the given configuration. It does not start any renderers — call Run to do that.

func (*App) NewBrowserWindow

func (a *App) NewBrowserWindow(cfg window.Config) *window.BrowserWindow

NewBrowserWindow creates a new BrowserWindow and spawns its own dedicated renderer process. Each window is fully independent — its own process, transport, and IPC connection. Safe to call multiple times for multi-window apps. Must be called from within the OnReady callback.

func (*App) OnClose

func (a *App) OnClose(fn func() bool)

OnClose registers a callback that fires when the last window is closed. Return true to allow the app to exit, false to suppress it.

func (*App) OnReady

func (a *App) OnReady(fn func())

OnReady registers a callback that fires once Run is called and the app is ready to create windows.

func (*App) Run

func (a *App) Run() error

Run starts the application and blocks until all windows are closed. OnReady is called synchronously so all NewBrowserWindow calls and their wg.Add(1) registrations complete before wg.Wait() is reached.

type AppConfig

type AppConfig struct {
	Title    string
	WebApp   bool
	Host     string
	Port     int
	Renderer RendererConfig
}

AppConfig is the top-level application configuration passed to NewApp.

type RendererConfig

type RendererConfig struct {
	// Path points at a locally built renderer binary (cmake output).
	Path string

	// Prebuilt enables automatic download of the renderer binary from
	// GitHub Releases when no cached binary exists. Ignored if Path is set.
	Prebuilt bool

	// Logging enables verbose renderer logging by passing --logging to the
	// renderer process. All renderer log lines are prefixed with [INFO],
	// [WARN], or [ERROR] and written to stderr.
	Logging bool
}

RendererConfig controls how Arc locates the native renderer binary.

Directories

Path Synopsis
cmd
arc module
cli module
internal

Jump to

Keyboard shortcuts

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