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 Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· 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) } | |||
| 71 | 71 | func (a Arg) Int() int32 { return int32(uint32(a)) } | |
| 72 | 72 | func (a Arg) Fixed() Fixed { return Fixed(int32(uint32(a))) } | |
| 73 | 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 | 85 | // Fd returns a received file descriptor. The handler owns it and must close it. | |
| 77 | 86 | 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() { | |||
| 127 | 127 | 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 | 146 | // Close destroys a display that has not started Run. It is a no-op once Run | |
| 131 | 147 | // has started; Run owns destruction in that case. Call before Run starts. | |
| 132 | 148 | func (d *Display) Close() { | |
| @@ -127,6 +127,22 @@ func (g *Global) Remove() { | |||||
| 127 | g.c = 0 | 127 | 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 Run | 146 | // 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) { | |||
| 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 | 63 | func TestDoStopped(t *testing.T) { | |
| 42 | 64 | d, err := NewDisplay() | |
| 43 | 65 | 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 ( | |||
| 32 | 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | |
| 33 | 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | |
| 34 | 34 | symErrnoLocation uintptr | |
| 35 | + | // wl_fixes support (libwayland 1.26+); zero on older libraries. | |
| 36 | + | symFixesAckGlobalRemove uintptr | |
| 35 | 37 | ||
| 36 | 38 | // Shared C callbacks. purego callbacks are never freed, so there is one | |
| 37 | 39 | // of each for the whole process. | |
| @@ -84,6 +86,7 @@ func load() error { | |||
| 84 | 86 | return | |
| 85 | 87 | } | |
| 86 | 88 | } | |
| 89 | + | symFixesAckGlobalRemove, _ = purego.Dlsym(lib, "wl_fixes_handle_ack_global_remove") | |
| 87 | 90 | symErrnoLocation, err = purego.Dlsym(lib, "__errno_location") | |
| 88 | 91 | if err != nil { | |
| 89 | 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 | 151 | func wlResourceDestroy(resource uintptr) { | |
| 149 | 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 | + | } | |
| @@ -32,6 +32,8 @@ var ( | |||||
| 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | 32 | symDisplayFlushClients, symEventLoopDispatch, symResourceCreate uintptr | ||
| 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | 33 | symResourceSetDispatcher, symResourcePostEventArr, symResourceDestroy, symResourceGetID uintptr | ||
| 34 | symErrnoLocation uintptr | 34 | 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 one | 38 | // 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 | return | 86 | 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 | + | } | |||