Go bindings for the Chromium Embedded Framework C API, using purego instead of cgo.
Linux only. CGO_ENABLED=0.
Note
purego-cef is usable today, but it is still pre-1.0.
Expect API changes while the handwritten cef layer settles.
purego-cef exposes the CEF C API through generated bindings and adds a smaller
handwritten cef layer for the common paths.
What to expect today:
- generated low-level bindings across the CEF C API
- a smaller ergonomic
ceflayer for init, clients, and common handlers - shipped mocks for testing
- ongoing API cleanup before 1.0
The binding layer is generated from CEF C headers (~114 files). The generator parses all headers and produces:
- Inbound port interfaces for every CEF handler and type
- Outbound port interfaces grouping CEF free functions and object methods by domain
- A composite CAPI interface embedding all outbound ports
- Typed constructors for handlers and wrappers for CEF objects with automatic refcount management
- C struct layouts and purego symbol registration
- Doc comments extracted from CEF C headers
The public cef package then adds a small handwritten layer for the places
where the raw C API is too awkward to expose directly.
A hand-written core domain handles UTF-16 string conversion, reference counting, CEF lifecycle orchestration, and the Bridge adapter implementing the CAPI interface.
Mocks for all interfaces are generated by Mockery v3 and shipped in cef/mocks/.
For most code, prefer the ergonomic handwritten layer when it exists:
cef.Settingsinstead ofcef.RawSettingscef.NewClient(...)withcef.Clientcef.NewLifeSpanHandler(...)withcef.LifeSpanHandlercef.NewAudioHandler(...)with the decodedcef.AudioHandlerinterfacecef.ExecuteSubprocess()when you want subprocess status and errors without implicitos.Exitorstderrwrites;cef.MaybeExitSubprocess()remains the short helper for main packagescef.ExecuteSubprocessWithApp(app)when helper subprocess startup also needs a customcef.Appcef.LifeSpanHandlerpopup callbacks receive a callback-scoped*cef.RawClientWriteSlotwhen CEF exposes a writable popup client out-param; check for nil before callingSetorClear
Treat Raw* types as advanced escape hatches.
For main packages, cef.MaybeExitSubprocess() is the short path. If you need
explicit subprocess status and error handling, use cef.ExecuteSubprocess() or
cef.ExecuteSubprocessWithApp(app).
package main
import (
"runtime"
"github.com/bnema/purego-cef/cef"
)
func main() {
runtime.LockOSThread()
cef.MaybeExitSubprocess()
if err := cef.Init(cef.DefaultSettings()); err != nil {
panic(err)
}
defer cef.Shutdown()
client := cef.NewClient(myClient{})
_ = client
// Your render loop here
for {
cef.DoMessageLoopWork()
}
}
// Implement the user-facing client interface — only the methods you need.
type myClient struct{}
func (c myClient) GetLifeSpanHandler() cef.LifeSpanHandler { return nil }
func (c myClient) GetRenderHandler() cef.RenderHandler { return myRenderer{} }
// ... other handler getters return nil- Go 1.26+
- CEF 147 runtime (the minimum accepted Chrome/CEF major; earlier versions are rejected at load time)
purego-cef locates libcef.so using this order:
CEF_DIRenvironment variable- Explicit
Settings.CEFDirargument passed tocef.Init() /usr/lib/cef(the Arch Linuxcefpackage install path), iflibcef.soexists there~/.local/share/cef
sudo pacman -S cefThis installs /usr/lib/cef/libcef.so and the required Resources — no env var needed.
CEF_VERSION=146sets the minimum accepted Chrome/CEF major to 146.CEF_SKIP_VERSION_CHECK=1bypasses the version guard entirely (diagnostics only; use with care).
CGO_ENABLED=0 go build ./...
CGO_ENABLED=0 go test ./...
Integration tests need the CEF runtime (adjust the directory as needed):
go test -tags=cef_integration ./integration
If your runtime is in a non-standard location, set CEF_DIR:
CEF_DIR=/custom/path go test -tags=cef_integration ./integration
go generate ./...
Or manually:
go run ./cmd/cefgen \
--headers-dir ~/.local/share/cef/include \
--capi-dir internal/capi \
--port-in-dir internal/ports/in \
--port-out-dir internal/ports/out \
--public-dir cef
mockery
cef/ public API (generated) + mocks
bridge.go hand-written: handler constructors, string helpers
init.go hand-written: Init(), Shutdown(), orchestration
internal/
ports/in/ inbound port interfaces (generated)
ports/out/ outbound port interfaces (generated) + composite CAPI (hand-written) + mocks
core/ domain logic: engine, string marshaling, refcount (hand-written)
capi/ purego bindings + bridge (generated + hand-written)
loader/ dlopen libcef.so + version validation (hand-written)
cmd/cefgen/ binding generator (parser, model, emitter, templates)
integration/ integration tests (require CEF runtime)
MIT