Skip to content

Add sovereign pure-Go Wayland backend with X11/Wayland auto-select - #2

Merged
tannevaled merged 5 commits into
mainfrom
feat/wayland-backend
Aug 8, 2026
Merged

Add sovereign pure-Go Wayland backend with X11/Wayland auto-select#2
tannevaled merged 5 commits into
mainfrom
feat/wayland-backend

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Adds an owned, pure-Go, CGO=0, zero-non-stdlib-dependency Wayland backend to
go-widgets/window, implementing the Wayland wire protocol from scratch over the
UNIX socket — the same sovereign approach as the existing internal/x11 backend
and go-freedesktop/dbus. window.Open() now auto-selects the backend: Wayland
when $WAYLAND_DISPLAY is set, else X11. The X11 backend is unchanged and fully
working.

Dated artifact: horodate 2026-08-08 22:21 CEST.

Phases

  • P1 — wire codec + connection (internal/wayland/wire.go, transport.go,
    conn.go): message framing (object-id / opcode / size) and the full typed-arg
    codec (int/uint/fixed 24.8/string/array/object/new_id); object-id allocator;
    wl_display (sync/get_registry, error + delete_id), wl_registry global
    enumeration + bind; Roundtrip. fd passing via SCM_RIGHTS over
    net.UnixConn WriteMsgUnix/ReadMsgUnix + syscall.UnixRights — no cgo.
    Native-order codec parametrised so the s390x lane exercises big-endian.
  • P2 — surface + shm present (surface.go, shm.go, xdgshell.go,
    present.go): wl_compositor, wl_shm, stable xdg_wm_base/xdg_surface/
    xdg_toplevel; portable anonymous shm (open+unlink+ftruncate+mmap) → create_pool
    (fd over SCM_RIGHTS) → ARGB8888 create_buffer; RGBA→ARGB pack, attach/damage/
    commit, double-buffer, frame-throttle callback, xdg_wm_base.pingpong,
    configure→ack.
  • P3 — input + lifecycle (seat.go, xkb.go, wlwindow.go): wl_seat
    wl_pointer (enter/leave/motion/button/axis) + wl_keyboard (keymap over an
    xkb_v1 shared-memory fd → parsed → keycode→keysym→rune / toolkit key name);
    modifiers/repeat; close + disconnect. All wired into the shared Run(root)
    host loop via a new Backend interface satisfied by both *Window (X11) and
    *wlWindow.

Auto-select

Open(Config) (Backend, error): Wayland when $WAYLAND_DISPLAY is set (socket
resolved against $XDG_RUNTIME_DIR), else X11 via $DISPLAY. Non-Linux stub
returns ErrUnsupported. A go-widgets app is backend-agnostic (Run/Size/Close/String).

Tests & CI

  • internal/wayland coverage: 100.0% incl every error branch — wire codec
    (both endian paths, fd args), registry, shm-pool math, xkb keymap parse,
    seat/pointer/keyboard event decode (keymap-fd mmap), and the pure
    event→toolkit mapping (100%). Real socketpair + scripted fake compositor +
    stub transport.
  • In-process fake-compositor integration test drives the whole handshake +
    a click + a key end to end (deterministic, no display server).
  • Live Wayland CI lane: headless sway (wlroots) → open a real toplevel,
    present a four-quadrant pattern via wl_shm, capture with grim, assert sampled
    pixels; best-effort key via wtype, honestly skipped when the headless seat
    exposes no keyboard.
  • Existing X11 lanes (Xvfb) kept; 100% gate now covers x11 and wayland;
    6-arch CGO=0 matrix (s390x big-endian wire codec), -race, darwin stub.

🤖 Generated with Claude Code

tannevaled and others added 5 commits August 8, 2026 22:37
From-scratch, pure-Go (CGO=0, zero non-stdlib dep) Wayland wire protocol,
mirroring the internal/x11 sovereign transport+codec approach.

- wire.go: message framing + typed-arg codec (int/uint/fixed 24.8/string/
  array/object/new_id), native-order parametrised for the s390x big-endian
  lane; bounds-checked decoder.
- transport.go: UNIX-socket transport with SCM_RIGHTS fd passing via
  net.UnixConn WriteMsgUnix/ReadMsgUnix (no cgo), stream reassembly, fd
  queue; injectable control-parser seam for full error-branch coverage.
- conn.go: object table + dispatcher, wl_display (sync/get_registry, error+
  delete_id), wl_callback, wl_registry global enumeration + bind, Roundtrip.

Table round-trip tests (both endian paths) incl fd args and every error
branch; real socketpair + scripted fake-compositor + stub transport.
internal/wayland coverage: 100.0%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- surface.go: wl_compositor bind + create_surface; wl_surface attach/damage/
  damage_buffer/commit/frame(throttle callback)/destroy.
- shm.go: portable anonymous shm (open+unlink+ftruncate+mmap, no memfd dep,
  cross-platform so it unit-tests on macOS too); wl_shm bind + format
  enumeration; wl_shm_pool.create_pool (fd over SCM_RIGHTS) + create_buffer
  ARGB8888; wl_buffer release tracking; injectable syscall seams for full
  error-branch coverage.
- xdgshell.go: xdg_wm_base (auto ping->pong) + get_xdg_surface; xdg_surface
  configure->ack; xdg_toplevel set_title/set_app_id + configure(size)/close.
- present.go: RGBA->ARGB8888 native-order packing; Registry bind helpers.

internal/wayland coverage: 100.0%; race-clean; 6-arch cross-compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
internal/wayland:
- seat.go: wl_seat (capabilities/name) -> wl_pointer (enter/leave/motion/
  button/axis) + wl_keyboard (keymap-over-fd, key, modifiers, repeat_info),
  callback-delivered; injectable mmap seam for the keymap fd.
- xkb.go: from-scratch xkb_v1 keymap parser (keycodes + symbols sections)
  -> keysym name -> rune / toolkit key name; minimal-but-functional table
  (letters/digits/punctuation/navigation/modifiers), documented scope.

window (backend-agnostic):
- Backend interface; X11 *Window and the new *wlWindow both satisfy it.
- wlwindow.go: full xdg-shell bring-up, double-buffered wl_shm present
  (RGBA->ARGB8888), pure event->toolkit translation (100%), resize/close,
  ping/pong, shared Run host loop.
- open_linux.go: Open auto-selects Wayland when $WAYLAND_DISPLAY is set,
  else X11; non-linux stub returns ErrUnsupported (Backend).

Tests: internal/wayland 100% (incl xkb parse, seat/pointer/keyboard event
decode, keymap-fd mmap, every error branch); pure event->toolkit mapping
100%; in-process scripted fake-compositor drives the whole handshake +
click + key end to end. race-clean; 6-arch cross-compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Extend the 100% coverage gate to internal/wayland alongside internal/x11.
- Add the live-wayland CI lane: launch a headless wlroots compositor (sway),
  open a real xdg-shell toplevel via the sovereign backend, present a known
  four-quadrant pattern through wl_shm, capture with grim and assert sampled
  pixels; best-effort key injection via wtype (virtual-keyboard), honestly
  skipped when the headless seat exposes no keyboard.
- Scope the X11 live lane to TestLiveX11; the 6-arch matrix already runs the
  whole module, so s390x exercises the Wayland wire codec big-endian path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The os.TempDir() fallback in createAnonFile is only taken when
XDG_RUNTIME_DIR is unset (true on macOS, false on Linux/CI), so the race
coverage gate saw 99.9% on the Linux runner. Exercise both the unset
(TempDir) and set branches explicitly so internal/wayland is a
deterministic 100% on every host. Verified 100% under -race on linux/arm64.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit eca6fb3 into main Aug 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant