detached 894fe61

feat(server): support wl_fixes registry destruction and global removal acks

commit
894fe61eb86962443e4cdd9c91beb2af45b11ac5
author
brice · · 2026-09-29 04:45 UTC
parent
ad674f5
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +54 -1
M server/args.go +10 -1
@@ -71,7 +71,16 @@ func (a Arg) Uint() uint32 { return uint32(a) }
7171 func (a Arg) Int() int32 { return int32(uint32(a)) }
7272 func (a Arg) Fixed() Fixed { return Fixed(int32(uint32(a))) }
7373 func (a Arg) NewID() uint32 { return uint32(a) }
74-func (a Arg) Resource() *Resource { return live.resources[uintptr(a)] }
74+
75+// Resource returns the object argument, nil for a null object. An object
76+// libwayland created itself (wl_registry, wl_callback of wl_display) comes
77+// back as a bare handle: Destroy works, it has no handler, version or client.
78+func (a Arg) Resource() *Resource {
79+ if r := live.resources[uintptr(a)]; r != nil || a == 0 {
80+ return r
81+ }
82+ return &Resource{c: uintptr(a)}
83+}
7584
7685 // Fd returns a received file descriptor. The handler owns it and must close it.
7786 func (a Arg) Fd() int { return int(int32(uint32(a))) }
@@ -71,7 +71,16 @@ func (a Arg) Uint() uint32 { return uint32(a) }
71 func (a Arg) Int() int32 { return int32(uint32(a)) }71 func (a Arg) Int() int32 { return int32(uint32(a)) }
72 func (a Arg) Fixed() Fixed { return Fixed(int32(uint32(a))) }72 func (a Arg) Fixed() Fixed { return Fixed(int32(uint32(a))) }
73 func (a Arg) NewID() uint32 { return uint32(a) }73 func (a Arg) NewID() uint32 { return uint32(a) }
74-func (a Arg) Resource() *Resource { return live.resources[uintptr(a)] }74+
75+// Resource returns the object argument, nil for a null object. An object
76+// libwayland created itself (wl_registry, wl_callback of wl_display) comes
77+// back as a bare handle: Destroy works, it has no handler, version or client.
78+func (a Arg) Resource() *Resource {
79+ if r := live.resources[uintptr(a)]; r != nil || a == 0 {
80+ return r
81+ }
82+ return &Resource{c: uintptr(a)}
83+}
75 84
76 // Fd returns a received file descriptor. The handler owns it and must close it.85 // Fd returns a received file descriptor. The handler owns it and must close it.
77 func (a Arg) Fd() int { return int(int32(uint32(a))) }86 func (a Arg) Fd() int { return int(int32(uint32(a))) }
M server/display.go +16 -0
@@ -127,6 +127,22 @@ func (g *Global) Remove() {
127127 g.c = 0
128128 }
129129
130+// AckGlobalRemove implements wl_fixes.ack_global_remove: libwayland checks
131+// that registry announced the removed global name, posting
132+// wl_fixes.invalid_ack_remove on fixes otherwise. It reports false when
133+// libwayland-server is older than 1.26 and has no support.
134+// Display goroutine only.
135+func AckGlobalRemove(fixes, registry *Resource, name uint32) bool {
136+ if symFixesAckGlobalRemove == 0 {
137+ return false
138+ }
139+ if fixes == nil || fixes.gone || registry == nil || registry.gone {
140+ return true
141+ }
142+ wlFixesHandleAckGlobalRemove(fixes.c, registry.c, name)
143+ return true
144+}
145+
130146 // Close destroys a display that has not started Run. It is a no-op once Run
131147 // has started; Run owns destruction in that case. Call before Run starts.
132148 func (d *Display) Close() {
@@ -127,6 +127,22 @@ func (g *Global) Remove() {
127 g.c = 0127 g.c = 0
128 }128 }
129 129
130+// AckGlobalRemove implements wl_fixes.ack_global_remove: libwayland checks
131+// that registry announced the removed global name, posting
132+// wl_fixes.invalid_ack_remove on fixes otherwise. It reports false when
133+// libwayland-server is older than 1.26 and has no support.
134+// Display goroutine only.
135+func AckGlobalRemove(fixes, registry *Resource, name uint32) bool {
136+ if symFixesAckGlobalRemove == 0 {
137+ return false
138+ }
139+ if fixes == nil || fixes.gone || registry == nil || registry.gone {
140+ return true
141+ }
142+ wlFixesHandleAckGlobalRemove(fixes.c, registry.c, name)
143+ return true
144+}
145+
130 // Close destroys a display that has not started Run. It is a no-op once Run146 // Close destroys a display that has not started Run. It is a no-op once Run
131 // has started; Run owns destruction in that case. Call before Run starts.147 // has started; Run owns destruction in that case. Call before Run starts.
132 func (d *Display) Close() {148 func (d *Display) Close() {
M server/display_test.go +22 -0
@@ -38,6 +38,28 @@ func TestNoHandlerClosesFDs(t *testing.T) {
3838 }
3939 }
4040
41+// Objects libwayland created itself (wl_registry) come back as bare handles.
42+func TestArgResourceUntracked(t *testing.T) {
43+ if Arg(0).Resource() != nil {
44+ t.Fatal("null object is not nil")
45+ }
46+ r := Arg(0x1234).Resource()
47+ if r == nil || r.c != 0x1234 || r.handler != nil {
48+ t.Fatalf("untracked object %+v", r)
49+ }
50+}
51+
52+func TestAckGlobalRemoveNilResources(t *testing.T) {
53+ if err := load(); err != nil {
54+ t.Skip(err)
55+ }
56+ // A dead or missing resource is ignored, whatever the library version.
57+ supported := AckGlobalRemove(nil, nil, 1)
58+ if supported != (symFixesAckGlobalRemove != 0) {
59+ t.Fatalf("supported %v with symbol %#x", supported, symFixesAckGlobalRemove)
60+ }
61+}
62+
4163 func TestDoStopped(t *testing.T) {
4264 d, err := NewDisplay()
4365 if err != nil {
@@ -38,6 +38,28 @@ func TestNoHandlerClosesFDs(t *testing.T) {
38 }38 }
39 }39 }
40 40
41+// Objects libwayland created itself (wl_registry) come back as bare handles.
42+func TestArgResourceUntracked(t *testing.T) {
43+ if Arg(0).Resource() != nil {
44+ t.Fatal("null object is not nil")
45+ }
46+ r := Arg(0x1234).Resource()
47+ if r == nil || r.c != 0x1234 || r.handler != nil {
48+ t.Fatalf("untracked object %+v", r)
49+ }
50+}
51+
52+func TestAckGlobalRemoveNilResources(t *testing.T) {
53+ if err := load(); err != nil {
54+ t.Skip(err)
55+ }
56+ // A dead or missing resource is ignored, whatever the library version.
57+ supported := AckGlobalRemove(nil, nil, 1)
58+ if supported != (symFixesAckGlobalRemove != 0) {
59+ t.Fatalf("supported %v with symbol %#x", supported, symFixesAckGlobalRemove)
60+ }
61+}
62+
41 func TestDoStopped(t *testing.T) {63 func TestDoStopped(t *testing.T) {
42 d, err := NewDisplay()64 d, err := NewDisplay()
43 if err != nil {65 if err != nil {
M server/lib.go +6 -0
@@ -32,6 +32,8 @@ var (
3232 symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr
3333 symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr
3434 symErrnoLocation uintptr
35+ // wl_fixes support (libwayland 1.26+); zero on older libraries.
36+ symFixesAckGlobalRemove uintptr
3537
3638 // Shared C callbacks. purego callbacks are never freed, so there is one
3739 // of each for the whole process.
@@ -84,6 +86,7 @@ func load() error {
8486 return
8587 }
8688 }
89+ symFixesAckGlobalRemove, _ = purego.Dlsym(lib, "wl_fixes_handle_ack_global_remove")
8790 symErrnoLocation, err = purego.Dlsym(lib, "__errno_location")
8891 if err != nil {
8992 libc, openErr := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_GLOBAL)
@@ -148,3 +151,6 @@ func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer
148151 func wlResourceDestroy(resource uintptr) {
149152 purego.Syscall6(symResourceDestroy, resource, 0, 0, 0, 0, 0)
150153 }
154+func wlFixesHandleAckGlobalRemove(fixes, registry uintptr, name uint32) {
155+ purego.Syscall6(symFixesAckGlobalRemove, fixes, registry, uintptr(name), 0, 0, 0)
156+}
@@ -32,6 +32,8 @@ var (
32 symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr32 symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr
33 symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr33 symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr
34 symErrnoLocation uintptr34 symErrnoLocation uintptr
35+ // wl_fixes support (libwayland 1.26+); zero on older libraries.
36+ symFixesAckGlobalRemove uintptr
35 37
36 // Shared C callbacks. purego callbacks are never freed, so there is one38 // Shared C callbacks. purego callbacks are never freed, so there is one
37 // of each for the whole process.39 // of each for the whole process.
@@ -84,6 +86,7 @@ func load() error {
84 return86 return
85 }87 }
86 }88 }
89+ symFixesAckGlobalRemove, _ = purego.Dlsym(lib, "wl_fixes_handle_ack_global_remove")
87 symErrnoLocation, err = purego.Dlsym(lib, "__errno_location")90 symErrnoLocation, err = purego.Dlsym(lib, "__errno_location")
88 if err != nil {91 if err != nil {
89 libc, openErr := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_GLOBAL)92 libc, openErr := purego.Dlopen("libc.so.6", purego.RTLD_NOW|purego.RTLD_GLOBAL)
@@ -148,3 +151,6 @@ func wlResourcePostEventArr(resource uintptr, opcode uint32, args unsafe.Pointer
148 func wlResourceDestroy(resource uintptr) {151 func wlResourceDestroy(resource uintptr) {
149 purego.Syscall6(symResourceDestroy, resource, 0, 0, 0, 0, 0)152 purego.Syscall6(symResourceDestroy, resource, 0, 0, 0, 0, 0)
150 }153 }
154+func wlFixesHandleAckGlobalRemove(fixes, registry uintptr, name uint32) {
155+ purego.Syscall6(symFixesAckGlobalRemove, fixes, registry, uintptr(name), 0, 0, 0)
156+}

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