v0.4.0 9c230c3

wire.go RawHistoryFolder

1package wlturbo
2
3import (
4	"encoding/binary"
5	"errors"
6	"fmt"
7	"io"
8)
9
10const (
11	// HeaderSize is the fixed size of a Wayland message header in bytes.
12	HeaderSize = 8
13
14	// displayObjectID is the well-known object ID of wl_display.
15	displayObjectID = 1
16
17	// DefaultMaxMessageSize bounds a single Wayland message, including its
18	// header. Larger messages are rejected instead of allocating.
19	DefaultMaxMessageSize = 1 << 20
20
21	// readChunkSize is the size of one read from the display socket.
22	readChunkSize = 64 * 1024
23)
24
25// Sentinel errors reported by the transport. Wire-level failures wrap one of
26// them so callers can classify with errors.Is, while ProtocolError carries the
27// offending object, opcode and size.
28var (
29	// ErrMalformedFrame reports a message whose header violates the wire
30	// protocol: too short, misaligned, or larger than the accepted maximum.
31	ErrMalformedFrame = errors.New("malformed wayland frame")
32
33	// ErrUnknownObject reports an event for an object this client never
34	// created or bound.
35	ErrUnknownObject = errors.New("wayland event for unknown object")
36
37	// ErrUnknownOpcode reports an event opcode the target object cannot receive.
38	ErrUnknownOpcode = errors.New("wayland event with unknown opcode")
39
40	// ErrDisplayError reports that the compositor sent wl_display.error.
41	ErrDisplayError = errors.New("wayland display error")
42)
43
44// ProtocolError describes a wire-level protocol violation.
45type ProtocolError struct {
46	Kind   string
47	Object uint32
48	Opcode uint16
49	Size   uint32
50	Err    error
51}
52
53func (e *ProtocolError) Error() string {
54	msg := e.Kind
55	if e.Object != 0 {
56		msg += fmt.Sprintf(" object=%d", e.Object)
57	}
58	if e.Kind == "unknown_opcode" || e.Kind == "display_error" {
59		msg += fmt.Sprintf(" opcode=%d", e.Opcode)
60	}
61	if e.Size != 0 {
62		msg += fmt.Sprintf(" size=%d", e.Size)
63	}
64	if e.Err != nil {
65		return fmt.Sprintf("wlturbo: %s: %v", msg, e.Err)
66	}
67	return "wlturbo: " + msg
68}
69
70// Unwrap returns the sentinel error classifying this violation.
71func (e *ProtocolError) Unwrap() error { return e.Err }
72
73// DisplayError is returned when the compositor reports wl_display.error.
74type DisplayError struct {
75	ObjectID uint32
76	Code     uint32
77	Message  string
78}
79
80func (e *DisplayError) Error() string {
81	if e.Message == "" {
82		return fmt.Sprintf("wlturbo: %v: object=%d code=%d", ErrDisplayError, e.ObjectID, e.Code)
83	}
84	return fmt.Sprintf("wlturbo: %v: object=%d code=%d: %s", ErrDisplayError, e.ObjectID, e.Code, e.Message)
85}
86
87// Unwrap reports DisplayError as ErrDisplayError.
88func (e *DisplayError) Unwrap() error { return ErrDisplayError }
89
90// receivedFrame is one complete Wayland message read from the connection.
91// Ancillary file descriptors are not part of the frame: they are owned by the
92// connection and consumed in arrival order by Event.Fd.
93type receivedFrame struct {
94	object uint32
95	opcode uint16
96	body   []byte
97}
98
99// parseHeader decodes and validates an 8-byte Wayland message header.
100func parseHeader(hdr []byte, maxSize uint32) (object uint32, opcode uint16, size uint32, err error) {
101	if len(hdr) < HeaderSize {
102		return 0, 0, 0, &ProtocolError{Kind: "short_header", Err: ErrMalformedFrame}
103	}
104	object = binary.LittleEndian.Uint32(hdr[0:4])
105	sizeOpcode := binary.LittleEndian.Uint32(hdr[4:8])
106	size = sizeOpcode >> 16
107	opcode = uint16(sizeOpcode & 0xffff)
108
109	switch {
110	case size < HeaderSize:
111		return 0, 0, 0, &ProtocolError{Kind: "header_size", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
112	case size%4 != 0:
113		return 0, 0, 0, &ProtocolError{Kind: "misaligned_size", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
114	case maxSize > 0 && size > maxSize:
115		return 0, 0, 0, &ProtocolError{Kind: "size_over_limit", Object: object, Opcode: opcode, Size: size, Err: ErrMalformedFrame}
116	}
117	return object, opcode, size, nil
118}
119
120// readFrame reads exactly one Wayland message from r. A short read never
121// yields a partial frame: the header and the declared body are both read in
122// full before returning. A maxSize of 0 means DefaultMaxMessageSize.
123func readFrame(r io.Reader, maxSize uint32) (object uint32, opcode uint16, body []byte, err error) {
124	if maxSize == 0 {
125		maxSize = DefaultMaxMessageSize
126	}
127	var hdr [HeaderSize]byte
128	if _, err = io.ReadFull(r, hdr[:]); err != nil {
129		return 0, 0, nil, err
130	}
131	object, opcode, size, err := parseHeader(hdr[:], maxSize)
132	if err != nil {
133		return 0, 0, nil, err
134	}
135	if size == HeaderSize {
136		return object, opcode, nil, nil
137	}
138	body = make([]byte, size-HeaderSize)
139	if _, err = io.ReadFull(r, body); err != nil {
140		return 0, 0, nil, err
141	}
142	return object, opcode, body, nil
143}
144
145type argKind uint8
146
147const (
148	argInvalid argKind = iota // zero Arg: rejected, never marshaled
149	argWord                   // uint, int, fixed: one 32-bit word
150	argString
151	argArray
152	argObject
153	argFD
154)
155
156// Arg is one typed request argument. It carries native values (no interface
157// boxing), so building and passing Args to Context.RequestArgs does not
158// allocate. The zero Arg is invalid and makes the request fail before any
159// write. Build Args with the ArgXxx constructors.
160type Arg struct {
161	kind argKind
162	word uint32
163	str  string
164	arr  []byte
165	obj  Object
166}
167
168// ArgUint is a uint argument.
169func ArgUint(v uint32) Arg { return Arg{kind: argWord, word: v} }
170
171// ArgInt is an int argument.
172func ArgInt(v int32) Arg { return Arg{kind: argWord, word: uint32(v)} }
173
174// ArgFixed is a fixed-point argument.
175func ArgFixed(v Fixed) Arg { return Arg{kind: argWord, word: uint32(v)} }
176
177// ArgString is a string argument.
178func ArgString(v string) Arg { return Arg{kind: argString, str: v} }
179
180// ArgArray is an array argument.
181func ArgArray(v []byte) Arg { return Arg{kind: argArray, arr: v} }
182
183// ArgObject is an object or new_id argument; a nil Object is sent as 0.
184func ArgObject(v Object) Arg { return Arg{kind: argObject, obj: v} }
185
186// ArgFD marks a file descriptor position. Descriptors travel out of band in
187// Request.FDs, so it contributes no bytes to the message body.
188func ArgFD() Arg { return Arg{kind: argFD} }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 8

↑↓ moveenter openesc close