x11

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: BSD-3-Clause Imports: 10 Imported by: 0

README

x11 — go-freedesktop

ci Go Reference License Go Coverage

The foundation every X11 client needs before it can say anything of its own — the wire codec, the .Xauthority parser, the connection-setup exchange, the anonymous shared-memory segment MIT-SHM attaches, and the unix-domain transport that hands the server a descriptor over SCM_RIGHTS.

Pure Go, CGO-free, standard library only. No Xlib, no XCB, no cgo — the wire format is encoded and decoded here, byte for byte, per the X11 protocol specification.

Scope — deliberately, the bytes and nothing above them

This package stops at the byte stream. It has no request table, no event loop and no opinion about what a client does with a connection, because the two things clients do with one — pump events for a window, or pull frames for a capture — want genuinely different request/reply machines over the same socket. Forcing them to share one would make both worse. A consumer builds its own connection type over [Handshake]'s result.

in out
Encoder / Decoder — the wire codec, both byte orders, every read bounds-checked request/reply/event demultiplexing
LoadAuthCookie, ParseXauthority — MIT-MAGIC-COOKIE-1 from $XAUTHORITY window creation, mapping, properties
Handshake + Setup — the setup exchange and its full reply (formats, screens, depths, visuals) keysym / keyboard mapping
Segment — an anonymous shared-memory region, mapped, for MIT-SHM the MIT-SHM, RANDR, XFIXES, Present request encodings
WrapUnix / DialUnix — the unix transport, SCM_RIGHTS fd passing, readability waiting DISPLAY parsing and socket-path search

Transport-agnostic on purpose

Handshake takes any io.ReadWriteCloser. In production that is a dialed unix socket; in a test it is one half of a net.Pipe driven by a scripted fake server. That is not a convenience — it is what lets the entire codec be tested on darwin and on windows, with no X server anywhere, so a protocol bug is caught on every platform rather than only on the one that can run an X server.

rw, err := x11.DialUnix("/tmp/.X11-unix/X0")
if err != nil {
    return err
}
name, data, err := x11.LoadAuthCookie(x11.AuthFilePath(), "", "0")
if err != nil {
    return err
}
setup, err := x11.Handshake(rw, binary.LittleEndian, name, data)
if err != nil {
    return err
}
// setup.Screens[0].RootVisualType() and setup.FormatFor(depth) are now
// everything you need to size and decode an image. Frame your own requests
// over rw from here.

Descriptor passing, and why it is here

MIT-SHM 1.2's AttachFd hands the X server a descriptor for a shared segment, so a full-screen image costs a ~40-byte request instead of megabytes down the socket. That needs two things that are not protocol: a segment (NewSegment — an unlinked file on a tmpfs, mapped MAP_SHARED, the portable equivalent of memfd_create) and a transport that can carry a descriptor (WrapUnix, whose result implements FDSender).

Which MIT-SHM requests a client then sends over them is the client's business and lives in the client: a capture asks the server to write into the segment, a window writes into it and asks the server to read.

Off Linux there is no segment to make: NewSegment reports ErrNoSharedMemory, and everything above it still builds and still passes.

Platforms

linux (amd64, arm64, riscv64, loong64, ppc64le, s390x) everything, against real syscalls
darwin, freebsd, other unix codec + xauth + setup + the unix transport; no shared memory
windows, js/wasm codec + xauth + setup; DialUnix reports ErrNoTransport

Both wire orders are exercised, and the big-endian path is executed on real big-endian (s390x under qemu) rather than merely compiled.

Tests & coverage

100% of statements, on Linux, darwin and windows, with no exemptions. Every line is either pure protocol arithmetic or a syscall behind a package variable a test can fail on purpose — so a number below 100 means a branch nobody has tested, not a platform getting in the way. The gate is .github/coverage-gate.sh and it runs on all three.

go test -coverprofile=cover.out ./... && ./.github/coverage-gate.sh cover.out

Consumers

License

BSD-3-Clause. See LICENSE.

Documentation

Overview

Package x11 is a from-scratch, pure-Go (CGO-free, no non-stdlib dependency) implementation of the pieces of the X Window System protocol, version 11.0, that every X client needs before it can say anything of its own: the wire encoder and decoder, the .Xauthority parser, the connection-setup exchange and its reply, the anonymous shared-memory segment MIT-SHM attaches, and the unix-domain transport that hands the server a descriptor over SCM_RIGHTS.

It deliberately stops there. It has no request table, no event loop and no opinion about what a client does with a connection: a screen capture and a window toolkit want very different request/reply machines on top of the same bytes, and this package is the bytes. A consumer builds its own connection type over Handshake's result.

Everything above the socket is transport-agnostic: Handshake takes any io.ReadWriteCloser, so the whole codec is exercisable in-process over a net.Pipe against a scripted fake server. That is what lets the wire format be tested to 100% on darwin and on windows, with no X server anywhere — a protocol bug is caught on every platform, not only on the one that can run an X server.

Index

Constants

View Source
const (
	VisualStaticGray  = 0
	VisualGrayScale   = 1
	VisualStaticColor = 2
	VisualPseudoColor = 3
	VisualTrueColor   = 4
	VisualDirectColor = 5
)

Visual classes. Only TrueColor and DirectColor decompose a pixel through the masks; the palette classes would need the colormap read back, which no display built this millennium presents.

View Source
const (
	ImageOrderLSB = 0
	ImageOrderMSB = 1
)

Image byte-order values reported by Setup.ImageByteOrder.

View Source
const (
	OrderLSB = 'l'
	OrderMSB = 'B'
)

The two byte-order sentinels sent as the first byte of the setup request: 'l' little-endian (LSB first), 'B' big-endian (MSB first).

View Source
const (
	FamilyInternet  = 0
	FamilyLocal     = 256
	FamilyWild      = 65535
	FamilyInternet6 = 6
)

Xauthority address families (X11/Xauth.h). Lengths in the file are always big-endian, independent of the machine.

View Source
const AuthMITCookie = "MIT-MAGIC-COOKIE-1"

AuthMITCookie is the MIT-MAGIC-COOKIE-1 authorization protocol name sent in the setup request when a matching cookie is found in the authority file.

Variables

View Source
var ErrNoSharedMemory = errors.New("x11: shared memory segments are only implemented on Linux")

ErrNoSharedMemory is reported off Linux, where no X server is dialed and so no shared segment is ever needed. Everything above it — the wire codec, the Xauthority parser, the setup exchange — is portable and is fully exercised here; only the shared memory and the socket are not.

View Source
var ErrNoTransport = errors.New("x11: dialing an X server over a unix socket is not implemented on this platform")

ErrNoTransport is reported where there is no unix-domain socket to dial — windows, js/wasm, plan9. The wire codec above this line is portable and is fully exercised on those platforms against an in-process scripted server; what is missing here is only the socket.

Functions

func AuthFilePath

func AuthFilePath() string

AuthFilePath returns the authority file to consult: $XAUTHORITY if set, otherwise $HOME/.Xauthority, otherwise "".

func DialUnix

func DialUnix(path string) (io.ReadWriteCloser, error)

DialUnix reports ErrNoTransport.

func EncodeAuthEntry

func EncodeAuthEntry(e AuthEntry) []byte

EncodeAuthEntry serializes an AuthEntry back to the Xauthority file format. It is the exact inverse of ParseXauthority, and is exported because the only honest way to test a client against a cookie is to write one: a test builds the fixture with this, and a round-trip through the parser proves the two agree.

func LoadAuthCookie

func LoadAuthCookie(authFile, host, display string) (name string, data []byte, err error)

LoadAuthCookie resolves the MIT-MAGIC-COOKIE-1 for (host, display) from the given authority file. A missing file, or no match, is not an error: it returns an empty name and data so the caller falls back to an unauthenticated setup, exactly as Xlib does. host defaults to the machine hostname when empty.

func Pad4

func Pad4(n int) int

Pad4 returns n rounded up to the next multiple of four. X11 pads every variable-length field to a four-byte boundary.

func Padding

func Padding(n int) int

Padding returns the number of pad bytes needed after n data bytes.

func ReadFull

func ReadFull(r io.Reader, b []byte) error

ReadFull reads exactly len(b) bytes or returns the first error. It is kept here so the codec's callers have one spelling of "read a whole packet".

func TrimNul

func TrimNul(b []byte) string

TrimNul returns b up to its first NUL, as a string. X11 zero-pads several text fields (a vendor string, a refusal reason, a format-8 property) and states their length separately or not at all.

func WrapUnix

func WrapUnix(c *net.UnixConn) io.ReadWriteCloser

WrapUnix reports ErrNoTransport by returning nil: there is no SCM_RIGHTS on this platform, so there is nothing a wrapper could add to the connection. A caller passes its own io.ReadWriteCloser to Handshake instead.

Types

type AuthEntry

type AuthEntry struct {
	Family  uint16
	Address []byte
	Number  string // display number as ASCII; "" is a wildcard
	Name    string // authorization protocol name
	Data    []byte // the cookie
}

AuthEntry is one record parsed from an Xauthority file.

func ParseXauthority

func ParseXauthority(r io.Reader) ([]AuthEntry, error)

ParseXauthority reads every entry from an Xauthority stream. The file is a flat sequence of records, each a big-endian-length-prefixed family/address/number/name/data quintuple.

type ByteOrder

type ByteOrder = binary.ByteOrder

ByteOrder is the wire byte order negotiated at connection setup. X11 lets the client pick; the server then speaks the client's order for the session.

func OrderFor

func OrderFor(sentinel byte) (ByteOrder, bool)

OrderFor maps a byte-order sentinel to its binary.ByteOrder. The bool reports whether the sentinel was recognised.

type Decoder

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

Decoder reads a fixed-order byte slice. Every read is bounds-checked; once the Decoder is not OK it stays that way, so a truncated buffer degrades to a clean error at the call site rather than a panic.

func NewDecoder

func NewDecoder(order ByteOrder, b []byte) *Decoder

NewDecoder wraps b for reading in the given order.

func (*Decoder) Get8

func (d *Decoder) Get8() byte

Get8 reads one byte.

func (*Decoder) Get16

func (d *Decoder) Get16() uint16

Get16 reads a 16-bit value in the decoder's order.

func (*Decoder) Get16s

func (d *Decoder) Get16s() int16

Get16s reads a signed 16-bit value, which is how X11 states coordinates.

func (*Decoder) Get32

func (d *Decoder) Get32() uint32

Get32 reads a 32-bit value in the decoder's order.

func (*Decoder) GetBytes

func (d *Decoder) GetBytes(n int) []byte

GetBytes returns the next n bytes (a copy) and advances.

func (*Decoder) GetString

func (d *Decoder) GetString(n int) string

GetString reads an n-byte string and skips its four-byte padding.

func (*Decoder) OK

func (d *Decoder) OK() bool

OK reports whether every read so far stayed inside the buffer. A parser checks it once, at the end, rather than after every field.

func (*Decoder) Order

func (d *Decoder) Order() ByteOrder

Order returns the byte order the Decoder reads in.

func (*Decoder) Skip

func (d *Decoder) Skip(n int)

Skip advances over n bytes, going not-OK rather than past the end.

type Depth

type Depth struct {
	Depth   uint8
	Visuals []VisualType
}

Depth groups the visuals available at a given colour depth.

type Encoder

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

Encoder builds a request body in a chosen byte order. Every multi-byte integer goes through the negotiated ByteOrder, so the same code emits a correct little- or big-endian stream.

func NewEncoder

func NewEncoder(order ByteOrder) *Encoder

NewEncoder starts an Encoder in the given order.

func (*Encoder) Bytes

func (e *Encoder) Bytes() []byte

Bytes returns the bytes written so far. The slice aliases the Encoder's own buffer, so a caller that keeps it past the next write must copy it.

func (*Encoder) Order

func (e *Encoder) Order() ByteOrder

Order returns the byte order the Encoder writes in.

func (*Encoder) Pad

func (e *Encoder) Pad(n int)

Pad appends the padding that follows n written bytes.

func (*Encoder) Put8

func (e *Encoder) Put8(v byte)

Put8 appends one byte.

func (*Encoder) Put16

func (e *Encoder) Put16(v uint16)

Put16 appends a 16-bit value in the negotiated order.

func (*Encoder) Put32

func (e *Encoder) Put32(v uint32)

Put32 appends a 32-bit value in the negotiated order.

func (*Encoder) PutBytes

func (e *Encoder) PutBytes(b []byte)

PutBytes appends raw bytes verbatim (no padding).

func (*Encoder) PutString

func (e *Encoder) PutString(s string)

PutString appends s then pads to a four-byte boundary with zero bytes.

func (*Encoder) Skip

func (e *Encoder) Skip(n int)

Skip appends n zero bytes (for "unused" fixed fields).

type FDSender

type FDSender interface {
	// SendFD writes one already-framed request with fd attached as a single
	// SCM_RIGHTS control message.
	SendFD(msg []byte, fd int) error
}

FDSender is implemented by a transport that can pass a file descriptor alongside a request over the same socket (a unix-domain stream, via SCM_RIGHTS). The transport WrapUnix returns implements it; an in-process net.Pipe used by a test does not, so a client's MIT-SHM fd-passing path degrades to the plain socket path when it is absent.

It is an interface rather than a concrete type because that is what keeps the connection above it transport-agnostic: a client type-asserts on this and never on a socket.

type Format

type Format struct {
	Depth       uint8
	BitsPerPix  uint8
	ScanlinePad uint8
}

Format is one entry of the server's pixmap-format list: for a given colour depth it fixes the bits-per-pixel and the scanline padding a ZPixmap image of that depth uses on the wire. It is what turns a width into a STRIDE.

func (Format) Stride

func (f Format) Stride(width int) int

Stride is the number of BYTES one scanline of a width-pixel ZPixmap image occupies in this format: the pixel bits rounded up to the format's scanline pad. It is not width*BitsPerPix/8 in general, which is exactly why an image must carry it rather than assume it.

type Screen

type Screen struct {
	Root          uint32
	DefaultColmap uint32
	WhitePixel    uint32
	BlackPixel    uint32
	Width         uint16
	Height        uint16
	WidthMM       uint16
	HeightMM      uint16
	RootVisual    uint32
	RootDepth     uint8
	Depths        []Depth
}

Screen is one root screen: its root window, size, root visual and the allowed depths (each carrying its visuals).

func (*Screen) DepthOfVisual

func (sc *Screen) DepthOfVisual(id uint32) (uint8, bool)

DepthOfVisual returns the colour depth the given visual lives at, and whether it was found.

func (*Screen) FindVisual

func (sc *Screen) FindVisual(id uint32) (VisualType, bool)

FindVisual returns the VisualType with the given id on screen sc, and whether it was found.

func (*Screen) RootVisualType

func (sc *Screen) RootVisualType() VisualType

RootVisualType returns the screen's root visual descriptor, falling back to a synthesized 24-bit TrueColor BGRX visual if the root visual id is somehow absent from the depth list (defensive; real servers always list it).

type Segment

type Segment struct {
	Seg  uint32
	FD   int
	Data []byte
	// contains filtered or unexported fields
}

Segment is a mapped anonymous shared-memory region backing a MIT-SHM attachment: Data is the pixel store both peers see, FD is the descriptor handed to the X server by the extension's AttachFd request, and Seg is the resource id the server knows it by.

It is the memory, not the protocol. Which AttachFd/PutImage/GetImage requests a client sends over it is the client's business — a capture asks the server to WRITE into the segment, a window writes into it and asks the server to read — and those requests live in the consumer, not here.

The lifecycle is portable; the shared-memory syscalls themselves sit behind createAnonFile/mmapRegion/munmapRegion/closeFD, which are provided per platform. Off Linux there is no X server to attach to, so createAnonFile reports ErrNoSharedMemory and no segment is ever created.

func NewSegment

func NewSegment(seg uint32, size int) (*Segment, error)

NewSegment allocates and maps a shared-memory segment of size bytes and assigns it the resource id seg. The caller registers it with the server via the MIT-SHM AttachFd request and frees it with Segment.Close.

func (*Segment) Close

func (s *Segment) Close() error

Close unmaps the region and closes its descriptor, returning the first error (both steps are attempted regardless). It is idempotent, so a deferred Close after an explicit one is harmless.

func (*Segment) Size

func (s *Segment) Size() int

Size returns the segment's byte length.

type Setup

type Setup struct {
	ProtoMajor     uint16
	ProtoMinor     uint16
	Release        uint32
	ResourceIDBase uint32
	ResourceIDMask uint32
	Vendor         string
	MaxRequestLen  uint16 // in 4-byte units
	ImageByteOrder uint8  // 0 = LSBFirst, 1 = MSBFirst
	BitmapBitOrder uint8
	BitmapUnit     uint8
	BitmapPad      uint8
	MinKeycode     uint8
	MaxKeycode     uint8
	Formats        []Format
	Screens        []Screen
}

Setup is the parsed server connection-setup reply: everything a client needs to allocate resource IDs, pick a visual, size images correctly and map keycodes.

func Handshake

func Handshake(rw io.ReadWriteCloser, order ByteOrder, authName string, authData []byte) (*Setup, error)

Handshake runs the client connection setup over rw: it sends the byte-order sentinel, protocol 11.0 and the authorization name and data, then reads and parses the server's reply. order selects the wire byte order; both are valid and the server adopts the client's choice for the whole session.

It returns only the parsed Setup. Framing requests and demultiplexing replies, errors and events is the caller's business, and the two things a client does with a connection — pump events, or push frames — want different machines over the same socket. What is common is everything up to this point, which is what this function is.

On return, rw is positioned exactly at the first byte the server sends after the setup reply, so the caller can start reading packets immediately. On error rw is left as it is: the caller closes it.

func (*Setup) FormatFor

func (s *Setup) FormatFor(depth uint8) (Format, bool)

FormatFor returns the pixmap Format matching depth, and whether one exists. It is what sizes each pixel and pads each scanline of an image at that depth.

func (*Setup) ScreenOf

func (s *Setup) ScreenOf(i int) *Screen

ScreenOf returns screen i, or nil when i names no screen.

func (*Setup) ScreenOfRoot

func (s *Setup) ScreenOfRoot(root uint32) (int, bool)

ScreenOfRoot returns the index of the screen whose root window is root, and whether one matched.

type SetupError

type SetupError struct {
	Reason       string
	Authenticate bool // the server asked for further authentication
}

SetupError is the connection-setup refusal: the server would not talk to us at all. Reason is the server's own wording, which for a missing or wrong cookie is "No protocol specified" or "Authorization required".

func (*SetupError) Error

func (e *SetupError) Error() string

Error renders the refusal.

type VisualType

type VisualType struct {
	ID          uint32
	Class       uint8
	BitsPerRGB  uint8
	ColormapEnt uint16
	RedMask     uint32
	GreenMask   uint32
	BlueMask    uint32
}

VisualType describes a visual: its class and the RGB channel masks a TrueColor or DirectColor visual packs a pixel with. The masks are what convert a pixel to or from any other layout.

func (VisualType) Direct

func (v VisualType) Direct() bool

Direct reports whether the visual's pixels decompose through its masks.

type Waiter

type Waiter interface {
	// WaitReadable reports whether the server sent anything within d. An
	// implementation must not consume a partial packet: see the note on the
	// unix transport for why a read deadline is not a substitute.
	WaitReadable(d time.Duration) bool
}

Waiter is implemented by a transport that can say whether the server sent anything, without reading a whole packet. The transport WrapUnix returns implements it.

It exists because parts of the X11 protocol have no timeout of their own — a selection paste asks whoever owns the clipboard and waits for an event that arrives only if that owner is alive and still answering. A client that cannot bound that wait freezes.

Jump to

Keyboard shortcuts

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