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}