detached 15e9011

client.go RawHistoryFolder

1package server
2
3import (
4	"errors"
5	"fmt"
6	"unsafe"
7
8	"github.com/bnema/purego"
9	"golang.org/x/sys/unix"
10)
11
12// FD returns the client's connection socket. The descriptor is borrowed from
13// libwayland: the caller must not close it, and it is valid only until the
14// client is destroyed (see OnDestroy). Use it to identify or inspect the
15// connection (for example fstat or /proc/self/fdinfo); reading or writing it
16// would corrupt the protocol stream. It returns an error for an invalid
17// (zero) Client. It does not allocate. Display goroutine only.
18func (c Client) FD() (int, error) {
19	if c.c == 0 {
20		return -1, errInvalidClient
21	}
22	r, _, _ := purego.Syscall6(symClientGetFD, c.c, 0, 0, 0, 0, 0)
23	return int(int32(r)), nil
24}
25
26// CreateClient adopts fd, an already connected stream socket (for example one
27// accepted on a listening socket the compositor owns), as a new client served
28// by this display.
29//
30// Ownership of fd: on success libwayland owns it and closes it when the
31// client is destroyed; the caller must not close it, nor use it for I/O. On
32// failure libwayland does not take it (it is left open) and the caller keeps
33// ownership and must close it. Sockets should be close-on-exec (accept with
34// SOCK_CLOEXEC); libwayland does not set the flag. fd must be a connected
35// unix socket: libwayland reads its peer credentials with SO_PEERCRED. A
36// socket whose peer has already closed is still adopted; the client is then
37// destroyed on the next dispatch.
38//
39// On failure the error wraps the errno libwayland left (for example ENOTSOCK
40// or ENOMEM), read right after the call on the same thread; it is reliable
41// when called on the locked display goroutine.
42//
43// The returned Client is valid until destroyed; register OnDestroy to learn
44// when. Display goroutine only, or before Run starts.
45func (d *Display) CreateClient(fd int) (Client, error) {
46	if d == nil || d.c == 0 {
47		return Client{}, errors.New("purego-libwayland: invalid display")
48	}
49	if fd < 0 {
50		return Client{}, fmt.Errorf("purego-libwayland: wl_client_create: invalid fd %d", fd)
51	}
52	r, _, _ := purego.Syscall6(symClientCreate, d.c, uintptr(fd), 0, 0, 0, 0)
53	if r == 0 {
54		return Client{}, fmt.Errorf("purego-libwayland: wl_client_create(%d) failed, fd not adopted: %w", fd, lastErrno())
55	}
56	return Client{r}, nil
57}
58
59// OnDestroy arranges for fn to run once when the client is destroyed, whether
60// it disconnected, was disconnected after a protocol error, or the display was
61// closed or stopped. Any number of functions may be registered per client;
62// each runs once, in registration order. There is no way to unregister one;
63// make fn a cheap no-op instead if it became irrelevant.
64//
65// fn runs on the display goroutine inside libwayland's client destruction. The
66// Client is still valid during fn (FD and Credentials work, resources are not
67// yet destroyed) but is dead afterwards: drop every copy of it, for example
68// delete it from a map[Client]T, because the handle value may be reused by a
69// later client.
70//
71// Registering another listener on the same client from inside fn is not
72// supported: whether it would run depends on how libwayland's final emit walks
73// the listener list (it differs between versions). Registering on a different,
74// live client from inside fn is fine.
75//
76// A Client is a comparable value (a handle to the C object) and is safe as a
77// map key from its creation or bind until fn runs.
78//
79// OnDestroy does nothing for a zero Client or nil fn. Registering on an
80// already destroyed client is invalid. It panics if the memory for the
81// listener cannot be mapped. Display goroutine only, or before Run starts.
82func (c Client) OnDestroy(fn func()) {
83	if c.c == 0 || fn == nil {
84		return
85	}
86	l := listeners.get()
87	live.clientGone[l] = fn
88	purego.Syscall6(symClientAddDestroyListener, c.c, l, 0, 0, 0, 0)
89}
90
91// listenerSlots hands out wl_listener structs in C-visible memory that the Go
92// GC cannot move or free. A slot is a struct wl_listener ({wl_list link;
93// wl_notify_func_t notify}, 24 bytes on LP64), padded to 32. Slots are
94// recycled through a free list, so only the peak number of concurrently
95// registered listeners is ever mapped; the chunks themselves are never
96// unmapped. Display goroutine only.
97type listenerSlots struct {
98	chunks [][]byte
99	free   []uintptr
100}
101
102const (
103	listenerSlot      = 32
104	listenerChunkSize = 4096
105)
106
107var listeners listenerSlots
108
109func (s *listenerSlots) get() uintptr {
110	if len(s.free) == 0 {
111		mem, err := unix.Mmap(-1, 0, listenerChunkSize, unix.PROT_READ|unix.PROT_WRITE, unix.MAP_PRIVATE|unix.MAP_ANONYMOUS)
112		if err != nil {
113			panic(fmt.Errorf("purego-libwayland: listener mmap: %w", err))
114		}
115		s.chunks = append(s.chunks, mem)
116		base := uintptr(unsafe.Pointer(&mem[0]))
117		for off := listenerChunkSize - listenerSlot; off >= 0; off -= listenerSlot {
118			s.free = append(s.free, base+uintptr(off))
119		}
120	}
121	l := s.free[len(s.free)-1]
122	s.free = s.free[:len(s.free)-1]
123	// notify lives at offset 16; wl_client_add_destroy_listener fills link.
124	// l is an address inside an mmap chunk kept in s.chunks.
125	*(*uintptr)(unsafe.Add(*(*unsafe.Pointer)(unsafe.Pointer(&l)), 16)) = cbClientGone
126	return l
127}
128
129func (s *listenerSlots) put(l uintptr) { s.free = append(s.free, l) }
130
131// clientGone is the wl_notify_func_t of every client destroy listener.
132func clientGone(l uintptr) {
133	fn := live.clientGone[l]
134	delete(live.clientGone, l)
135	// libwayland's emit loop first unlinks each node and, on current versions,
136	// resets it with wl_list_init, so this is a no-op on a self-linked node;
137	// on older versions the node may still be linked and must be unlinked
138	// before the slot can be reused.
139	purego.Syscall6(symListRemove, l, 0, 0, 0, 0, 0)
140	listeners.put(l)
141	if fn != nil {
142		fn()
143	}
144}

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 14

↑↓ moveenter openesc close