feat(registry): negotiate announced global versions for extension binds
- commit
- ef3605f0837bce55228ebf8bfd321f2b406c6841
- author
- brice · · 2026-09-28 08:04 UTC
- parent
- 4bb8af0
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
6 files · +76 -54
M README.md +2 -0
| @@ -21,6 +21,8 @@ Higher-level protocol implementations (virtual input devices, output management, | |||
| 21 | 21 | ||
| 22 | 22 | `protocol/core` provides core Wayland (including data-device); `protocol/xdgshell` provides xdg-shell; `protocol/linuxdmabuf` provides linux-dmabuf v4 feedback; `protocol/drmsyncobj` provides linux-drm-syncobj v1; `protocol/viewporter` provides viewporter; `protocol/fractionalscale` provides fractional-scale v1; and `protocol/textinput` provides text-input v3. Bindings handle wire messages and object lifecycle; applications decide compositor policy. | |
| 23 | 23 | ||
| 24 | + | After `display.Roundtrip()` discovers globals, call `display.Registry().BindNegotiated(interfaceName, supportedVersion, proxy)` to bind at the lesser of the advertised and supported versions. It returns the negotiated version; use `errors.Is(err, wlturbo.ErrGlobalNotFound)` to handle absent optional globals. | |
| 25 | + | ||
| 24 | 26 | ## Features | |
| 25 | 27 | ||
| 26 | 28 | ### Performance Optimizations | |
| @@ -21,6 +21,8 @@ Higher-level protocol implementations (virtual input devices, output management, | |||||
| 21 | 21 | ||||
| 22 | `protocol/core` provides core Wayland (including data-device); `protocol/xdgshell` provides xdg-shell; `protocol/linuxdmabuf` provides linux-dmabuf v4 feedback; `protocol/drmsyncobj` provides linux-drm-syncobj v1; `protocol/viewporter` provides viewporter; `protocol/fractionalscale` provides fractional-scale v1; and `protocol/textinput` provides text-input v3. Bindings handle wire messages and object lifecycle; applications decide compositor policy. | 22 | `protocol/core` provides core Wayland (including data-device); `protocol/xdgshell` provides xdg-shell; `protocol/linuxdmabuf` provides linux-dmabuf v4 feedback; `protocol/drmsyncobj` provides linux-drm-syncobj v1; `protocol/viewporter` provides viewporter; `protocol/fractionalscale` provides fractional-scale v1; and `protocol/textinput` provides text-input v3. Bindings handle wire messages and object lifecycle; applications decide compositor policy. | ||
| 23 | 23 | ||||
| 24 | + | After `display.Roundtrip()` discovers globals, call `display.Registry().BindNegotiated(interfaceName, supportedVersion, proxy)` to bind at the lesser of the advertised and supported versions. It returns the negotiated version; use `errors.Is(err, wlturbo.ErrGlobalNotFound)` to handle absent optional globals. | |||
| 25 | + | ||||
| 24 | ## Features | 26 | ## Features | ||
| 25 | 27 | ||||
| 26 | ### Performance Optimizations | 28 | ### Performance Optimizations | ||
M client.go +22 -0
| @@ -789,6 +789,28 @@ func (r *Registry) AddHandler(iface string, handler GlobalHandler) { | |||
| 789 | 789 | r.handlers[iface] = handler | |
| 790 | 790 | } | |
| 791 | 791 | ||
| 792 | + | // ErrGlobalNotFound reports that the compositor does not announce a global. | |
| 793 | + | var ErrGlobalNotFound = errors.New("wlturbo: global not announced") | |
| 794 | + | ||
| 795 | + | // BindNegotiated binds the announced global for iface at | |
| 796 | + | // min(announced version, supported) and returns that version. It returns | |
| 797 | + | // ErrGlobalNotFound when the global is absent, so callers can decide whether | |
| 798 | + | // the capability is optional. | |
| 799 | + | func (r *Registry) BindNegotiated(iface string, supported uint32, proxy Proxy) (uint32, error) { | |
| 800 | + | if supported == 0 { | |
| 801 | + | return 0, fmt.Errorf("wlturbo: supported version for %s must be at least 1", iface) | |
| 802 | + | } | |
| 803 | + | g, ok := r.FindGlobal(iface) | |
| 804 | + | if !ok || g.Version == 0 { | |
| 805 | + | return 0, fmt.Errorf("%w: %s", ErrGlobalNotFound, iface) | |
| 806 | + | } | |
| 807 | + | version := min(g.Version, supported) | |
| 808 | + | if err := r.Bind(g.Name, iface, version, proxy); err != nil { | |
| 809 | + | return 0, err | |
| 810 | + | } | |
| 811 | + | return version, nil | |
| 812 | + | } | |
| 813 | + | ||
| 792 | 814 | // Bind binds to a global object and returns a typed proxy | |
| 793 | 815 | func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) error { | |
| 794 | 816 | // Set the ID if not already set | |
| @@ -789,6 +789,28 @@ func (r *Registry) AddHandler(iface string, handler GlobalHandler) { | |||||
| 789 | r.handlers[iface] = handler | 789 | r.handlers[iface] = handler | ||
| 790 | } | 790 | } | ||
| 791 | 791 | ||||
| 792 | + | // ErrGlobalNotFound reports that the compositor does not announce a global. | |||
| 793 | + | var ErrGlobalNotFound = errors.New("wlturbo: global not announced") | |||
| 794 | + | ||||
| 795 | + | // BindNegotiated binds the announced global for iface at | |||
| 796 | + | // min(announced version, supported) and returns that version. It returns | |||
| 797 | + | // ErrGlobalNotFound when the global is absent, so callers can decide whether | |||
| 798 | + | // the capability is optional. | |||
| 799 | + | func (r *Registry) BindNegotiated(iface string, supported uint32, proxy Proxy) (uint32, error) { | |||
| 800 | + | if supported == 0 { | |||
| 801 | + | return 0, fmt.Errorf("wlturbo: supported version for %s must be at least 1", iface) | |||
| 802 | + | } | |||
| 803 | + | g, ok := r.FindGlobal(iface) | |||
| 804 | + | if !ok || g.Version == 0 { | |||
| 805 | + | return 0, fmt.Errorf("%w: %s", ErrGlobalNotFound, iface) | |||
| 806 | + | } | |||
| 807 | + | version := min(g.Version, supported) | |||
| 808 | + | if err := r.Bind(g.Name, iface, version, proxy); err != nil { | |||
| 809 | + | return 0, err | |||
| 810 | + | } | |||
| 811 | + | return version, nil | |||
| 812 | + | } | |||
| 813 | + | ||||
| 792 | // Bind binds to a global object and returns a typed proxy | 814 | // Bind binds to a global object and returns a typed proxy | ||
| 793 | func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) error { | 815 | func (r *Registry) Bind(name uint32, iface string, version uint32, proxy Proxy) error { | ||
| 794 | // Set the ID if not already set | 816 | // Set the ID if not already set | ||
M consumer_test.go +8 -1
| @@ -47,7 +47,14 @@ func TestExternalConsumer(t *testing.T) { | |||
| 47 | 47 | if err := os.WriteFile(filepath.Join(temp, "extensions", "extensions_test.go"), extension, 0600); err != nil { | |
| 48 | 48 | t.Fatal(err) | |
| 49 | 49 | } | |
| 50 | - | cmd := exec.Command(goBin, "test", "-run", "TestGeneratedCoreOverSocketpair|TestBufferReleaseAndOfferChild|TestExtensionsOverSocketpair", "./...") | |
| 50 | + | negotiation, err := os.ReadFile(filepath.Join(root, "protocol/negotiation_test.go")) | |
| 51 | + | if err != nil { | |
| 52 | + | t.Fatal(err) | |
| 53 | + | } | |
| 54 | + | if err := os.WriteFile(filepath.Join(temp, "extensions", "negotiation_test.go"), negotiation, 0600); err != nil { | |
| 55 | + | t.Fatal(err) | |
| 56 | + | } | |
| 57 | + | cmd := exec.Command(goBin, "test", "-run", "TestGeneratedCoreOverSocketpair|TestBufferReleaseAndOfferChild|TestExtensionsOverSocketpair|TestAnnouncedVersionNegotiation", "./...") | |
| 51 | 58 | cmd.Dir = temp | |
| 52 | 59 | cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=-mod=mod") | |
| 53 | 60 | if output, err := cmd.CombinedOutput(); err != nil { | |
| @@ -47,7 +47,14 @@ func TestExternalConsumer(t *testing.T) { | |||||
| 47 | if err := os.WriteFile(filepath.Join(temp, "extensions", "extensions_test.go"), extension, 0600); err != nil { | 47 | if err := os.WriteFile(filepath.Join(temp, "extensions", "extensions_test.go"), extension, 0600); err != nil { | ||
| 48 | t.Fatal(err) | 48 | t.Fatal(err) | ||
| 49 | } | 49 | } | ||
| 50 | - | cmd := exec.Command(goBin, "test", "-run", "TestGeneratedCoreOverSocketpair|TestBufferReleaseAndOfferChild|TestExtensionsOverSocketpair", "./...") | 50 | + | negotiation, err := os.ReadFile(filepath.Join(root, "protocol/negotiation_test.go")) |
| 51 | + | if err != nil { | |||
| 52 | + | t.Fatal(err) | |||
| 53 | + | } | |||
| 54 | + | if err := os.WriteFile(filepath.Join(temp, "extensions", "negotiation_test.go"), negotiation, 0600); err != nil { | |||
| 55 | + | t.Fatal(err) | |||
| 56 | + | } | |||
| 57 | + | cmd := exec.Command(goBin, "test", "-run", "TestGeneratedCoreOverSocketpair|TestBufferReleaseAndOfferChild|TestExtensionsOverSocketpair|TestAnnouncedVersionNegotiation", "./...") | |||
| 51 | cmd.Dir = temp | 58 | cmd.Dir = temp | ||
| 52 | cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=-mod=mod") | 59 | cmd.Env = append(os.Environ(), "GOWORK=off", "GOFLAGS=-mod=mod") | ||
| 53 | if output, err := cmd.CombinedOutput(); err != nil { | 60 | if output, err := cmd.CombinedOutput(); err != nil { | ||
M protocol/extensions_test.go +27 -16
| @@ -94,16 +94,19 @@ func noerr(t *testing.T, e error) { | |||
| 94 | 94 | t.Fatal(e) | |
| 95 | 95 | } | |
| 96 | 96 | } | |
| 97 | - | func bind(t *testing.T, d *wlturbo.Display, p *net.UnixConn, name uint32, iface string, supported uint32, proxy wlturbo.Proxy) { | |
| 97 | + | func bind(t *testing.T, d *wlturbo.Display, p *net.UnixConn, name uint32, iface string, announced uint32, supported uint32, proxy wlturbo.Proxy) { | |
| 98 | 98 | t.Helper() | |
| 99 | - | v := supported | |
| 100 | - | if v > 4 { | |
| 101 | - | v = 4 | |
| 102 | - | } | |
| 103 | - | noerr(t, d.Registry().Bind(name, iface, v, proxy)) | |
| 99 | + | // A fake server must advertise a global before clients can negotiate it. | |
| 100 | + | announcement := append(msg(0, 0, name)[8:], str(iface)...) | |
| 101 | + | announcement = append(announcement, msg(0, 0, announced)[8:]...) | |
| 102 | + | send(t, p, payload(d.Registry().ID(), 0, announcement...)) | |
| 103 | + | noerr(t, d.Dispatch()) | |
| 104 | + | v, err := d.Registry().BindNegotiated(iface, supported, proxy) | |
| 105 | + | noerr(t, err) | |
| 106 | + | want := min(announced, supported) | |
| 104 | 107 | _, op, b := request(t, p) | |
| 105 | - | if op != 0 || binary.NativeEndian.Uint32(b) != name || binary.NativeEndian.Uint32(b[len(b)-8:]) != v || binary.NativeEndian.Uint32(b[len(b)-4:]) != proxy.ID() { | |
| 106 | - | t.Fatalf("bad bind %s version=%d %x", iface, v, b) | |
| 108 | + | if op != 0 || v != want || binary.NativeEndian.Uint32(b) != name || binary.NativeEndian.Uint32(b[len(b)-8:]) != want || binary.NativeEndian.Uint32(b[len(b)-4:]) != proxy.ID() { | |
| 109 | + | t.Fatalf("bad bind %s announced=%d supported=%d negotiated=%d wire=%x", iface, announced, supported, v, b) | |
| 107 | 110 | } | |
| 108 | 111 | } | |
| 109 | 112 | func TestExtensionsOverSocketpair(t *testing.T) { | |
| @@ -121,6 +124,10 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 121 | 124 | if _, ok := d.Registry().FindGlobal(textinput.TextInputManagerV3Interface); ok { | |
| 122 | 125 | t.Fatal("absent text input") | |
| 123 | 126 | } | |
| 127 | + | // Remove the discovery-only announcement before the bind matrix assigns | |
| 128 | + | // its own global name to this interface. | |
| 129 | + | send(t, p, msg(d.Registry().ID(), 1, 11)) | |
| 130 | + | noerr(t, d.Dispatch()) | |
| 124 | 131 | surface := core.NewSurface(d.Context()) | |
| 125 | 132 | surface.SetID(d.AllocateID()) | |
| 126 | 133 | d.Context().Register(surface) | |
| @@ -128,7 +135,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 128 | 135 | seat.SetID(d.AllocateID()) | |
| 129 | 136 | d.Context().Register(seat) | |
| 130 | 137 | wm := xdgshell.NewXdgWmBase(d.Context()) | |
| 131 | - | bind(t, d, p, 1, xdgshell.XdgWmBaseInterface, 7, wm) | |
| 138 | + | bind(t, d, p, 1, xdgshell.XdgWmBaseInterface, 6, 7, wm) | |
| 132 | 139 | xs, e := wm.GetXdgSurface(surface) | |
| 133 | 140 | noerr(t, e) | |
| 134 | 141 | request(t, p) | |
| @@ -158,7 +165,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 158 | 165 | noerr(t, wm.Destroy()) | |
| 159 | 166 | request(t, p) | |
| 160 | 167 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | |
| 161 | - | bind(t, d, p, 2, linuxdmabuf.LinuxDmabufInterface, 6, dm) | |
| 168 | + | bind(t, d, p, 2, linuxdmabuf.LinuxDmabufInterface, 6, 4, dm) | |
| 162 | 169 | fb, e := dm.GetDefaultFeedback() | |
| 163 | 170 | noerr(t, e) | |
| 164 | 171 | request(t, p) | |
| @@ -270,7 +277,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 270 | 277 | noerr(t, dm.Destroy()) | |
| 271 | 278 | request(t, p) | |
| 272 | 279 | sync := drmsyncobj.NewWpLinuxDrmSyncobjManager(d.Context()) | |
| 273 | - | bind(t, d, p, 3, drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, sync) | |
| 280 | + | bind(t, d, p, 3, drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, 1, sync) | |
| 274 | 281 | ss, e := sync.GetSurface(surface) | |
| 275 | 282 | noerr(t, e) | |
| 276 | 283 | request(t, p) | |
| @@ -300,7 +307,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 300 | 307 | noerr(t, sync.Destroy()) | |
| 301 | 308 | request(t, p) | |
| 302 | 309 | vp := viewporter.NewWpViewporter(d.Context()) | |
| 303 | - | bind(t, d, p, 4, viewporter.WpViewporterInterface, 1, vp) | |
| 310 | + | bind(t, d, p, 4, viewporter.WpViewporterInterface, 1, 1, vp) | |
| 304 | 311 | view, e := vp.GetViewport(surface) | |
| 305 | 312 | noerr(t, e) | |
| 306 | 313 | request(t, p) | |
| @@ -309,7 +316,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 309 | 316 | noerr(t, vp.Destroy()) | |
| 310 | 317 | request(t, p) | |
| 311 | 318 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | |
| 312 | - | bind(t, d, p, 5, fractionalscale.WpFractionalScaleManagerInterface, 1, fs) | |
| 319 | + | bind(t, d, p, 5, fractionalscale.WpFractionalScaleManagerInterface, 1, 1, fs) | |
| 313 | 320 | scale, e := fs.GetFractionalScale(surface) | |
| 314 | 321 | noerr(t, e) | |
| 315 | 322 | request(t, p) | |
| @@ -325,7 +332,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 325 | 332 | noerr(t, fs.Destroy()) | |
| 326 | 333 | request(t, p) | |
| 327 | 334 | ti := textinput.NewTextInputManagerV3(d.Context()) | |
| 328 | - | bind(t, d, p, 6, textinput.TextInputManagerV3Interface, 1, ti) | |
| 335 | + | bind(t, d, p, 6, textinput.TextInputManagerV3Interface, 2, 1, ti) | |
| 329 | 336 | input, e := ti.GetTextInput(seat) | |
| 330 | 337 | noerr(t, e) | |
| 331 | 338 | request(t, p) | |
| @@ -362,7 +369,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||
| 362 | 369 | // Core data-device remains in protocol/core; verify its typed child and | |
| 363 | 370 | // both directions of clipboard descriptor ownership. | |
| 364 | 371 | mgr := core.NewDataDeviceManager(d.Context()) | |
| 365 | - | bind(t, d, p, 7, core.DataDeviceManagerInterface, 3, mgr) | |
| 372 | + | bind(t, d, p, 7, core.DataDeviceManagerInterface, 3, 4, mgr) | |
| 366 | 373 | device, e := mgr.GetDataDevice(seat) | |
| 367 | 374 | noerr(t, e) | |
| 368 | 375 | request(t, p) | |
| @@ -442,7 +449,11 @@ func TestLiveFeedback(t *testing.T) { | |||
| 442 | 449 | t.Fatal("dmabuf feedback requires v4") | |
| 443 | 450 | } | |
| 444 | 451 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | |
| 445 | - | noerr(t, d.Registry().Bind(g.Name, iface, v, dm)) | |
| 452 | + | negotiated, err := d.Registry().BindNegotiated(iface, 4, dm) | |
| 453 | + | noerr(t, err) | |
| 454 | + | if negotiated != v { | |
| 455 | + | t.Fatalf("dmabuf negotiated=%d want=%d", negotiated, v) | |
| 456 | + | } | |
| 446 | 457 | fb, e := dm.GetDefaultFeedback() | |
| 447 | 458 | noerr(t, e) | |
| 448 | 459 | done := false | |
| @@ -94,16 +94,19 @@ func noerr(t *testing.T, e error) { | |||||
| 94 | t.Fatal(e) | 94 | t.Fatal(e) | ||
| 95 | } | 95 | } | ||
| 96 | } | 96 | } | ||
| 97 | - | func bind(t *testing.T, d *wlturbo.Display, p *net.UnixConn, name uint32, iface string, supported uint32, proxy wlturbo.Proxy) { | 97 | + | func bind(t *testing.T, d *wlturbo.Display, p *net.UnixConn, name uint32, iface string, announced uint32, supported uint32, proxy wlturbo.Proxy) { |
| 98 | t.Helper() | 98 | t.Helper() | ||
| 99 | - | v := supported | 99 | + | // A fake server must advertise a global before clients can negotiate it. |
| 100 | - | if v > 4 { | 100 | + | announcement := append(msg(0, 0, name)[8:], str(iface)...) |
| 101 | - | v = 4 | 101 | + | announcement = append(announcement, msg(0, 0, announced)[8:]...) |
| 102 | - | } | 102 | + | send(t, p, payload(d.Registry().ID(), 0, announcement...)) |
| 103 | - | noerr(t, d.Registry().Bind(name, iface, v, proxy)) | 103 | + | noerr(t, d.Dispatch()) |
| 104 | + | v, err := d.Registry().BindNegotiated(iface, supported, proxy) | |||
| 105 | + | noerr(t, err) | |||
| 106 | + | want := min(announced, supported) | |||
| 104 | _, op, b := request(t, p) | 107 | _, op, b := request(t, p) | ||
| 105 | - | if op != 0 || binary.NativeEndian.Uint32(b) != name || binary.NativeEndian.Uint32(b[len(b)-8:]) != v || binary.NativeEndian.Uint32(b[len(b)-4:]) != proxy.ID() { | 108 | + | if op != 0 || v != want || binary.NativeEndian.Uint32(b) != name || binary.NativeEndian.Uint32(b[len(b)-8:]) != want || binary.NativeEndian.Uint32(b[len(b)-4:]) != proxy.ID() { |
| 106 | - | t.Fatalf("bad bind %s version=%d %x", iface, v, b) | 109 | + | t.Fatalf("bad bind %s announced=%d supported=%d negotiated=%d wire=%x", iface, announced, supported, v, b) |
| 107 | } | 110 | } | ||
| 108 | } | 111 | } | ||
| 109 | func TestExtensionsOverSocketpair(t *testing.T) { | 112 | func TestExtensionsOverSocketpair(t *testing.T) { | ||
| @@ -121,6 +124,10 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 121 | if _, ok := d.Registry().FindGlobal(textinput.TextInputManagerV3Interface); ok { | 124 | if _, ok := d.Registry().FindGlobal(textinput.TextInputManagerV3Interface); ok { | ||
| 122 | t.Fatal("absent text input") | 125 | t.Fatal("absent text input") | ||
| 123 | } | 126 | } | ||
| 127 | + | // Remove the discovery-only announcement before the bind matrix assigns | |||
| 128 | + | // its own global name to this interface. | |||
| 129 | + | send(t, p, msg(d.Registry().ID(), 1, 11)) | |||
| 130 | + | noerr(t, d.Dispatch()) | |||
| 124 | surface := core.NewSurface(d.Context()) | 131 | surface := core.NewSurface(d.Context()) | ||
| 125 | surface.SetID(d.AllocateID()) | 132 | surface.SetID(d.AllocateID()) | ||
| 126 | d.Context().Register(surface) | 133 | d.Context().Register(surface) | ||
| @@ -128,7 +135,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 128 | seat.SetID(d.AllocateID()) | 135 | seat.SetID(d.AllocateID()) | ||
| 129 | d.Context().Register(seat) | 136 | d.Context().Register(seat) | ||
| 130 | wm := xdgshell.NewXdgWmBase(d.Context()) | 137 | wm := xdgshell.NewXdgWmBase(d.Context()) | ||
| 131 | - | bind(t, d, p, 1, xdgshell.XdgWmBaseInterface, 7, wm) | 138 | + | bind(t, d, p, 1, xdgshell.XdgWmBaseInterface, 6, 7, wm) |
| 132 | xs, e := wm.GetXdgSurface(surface) | 139 | xs, e := wm.GetXdgSurface(surface) | ||
| 133 | noerr(t, e) | 140 | noerr(t, e) | ||
| 134 | request(t, p) | 141 | request(t, p) | ||
| @@ -158,7 +165,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 158 | noerr(t, wm.Destroy()) | 165 | noerr(t, wm.Destroy()) | ||
| 159 | request(t, p) | 166 | request(t, p) | ||
| 160 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | 167 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | ||
| 161 | - | bind(t, d, p, 2, linuxdmabuf.LinuxDmabufInterface, 6, dm) | 168 | + | bind(t, d, p, 2, linuxdmabuf.LinuxDmabufInterface, 6, 4, dm) |
| 162 | fb, e := dm.GetDefaultFeedback() | 169 | fb, e := dm.GetDefaultFeedback() | ||
| 163 | noerr(t, e) | 170 | noerr(t, e) | ||
| 164 | request(t, p) | 171 | request(t, p) | ||
| @@ -270,7 +277,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 270 | noerr(t, dm.Destroy()) | 277 | noerr(t, dm.Destroy()) | ||
| 271 | request(t, p) | 278 | request(t, p) | ||
| 272 | sync := drmsyncobj.NewWpLinuxDrmSyncobjManager(d.Context()) | 279 | sync := drmsyncobj.NewWpLinuxDrmSyncobjManager(d.Context()) | ||
| 273 | - | bind(t, d, p, 3, drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, sync) | 280 | + | bind(t, d, p, 3, drmsyncobj.WpLinuxDrmSyncobjManagerInterface, 1, 1, sync) |
| 274 | ss, e := sync.GetSurface(surface) | 281 | ss, e := sync.GetSurface(surface) | ||
| 275 | noerr(t, e) | 282 | noerr(t, e) | ||
| 276 | request(t, p) | 283 | request(t, p) | ||
| @@ -300,7 +307,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 300 | noerr(t, sync.Destroy()) | 307 | noerr(t, sync.Destroy()) | ||
| 301 | request(t, p) | 308 | request(t, p) | ||
| 302 | vp := viewporter.NewWpViewporter(d.Context()) | 309 | vp := viewporter.NewWpViewporter(d.Context()) | ||
| 303 | - | bind(t, d, p, 4, viewporter.WpViewporterInterface, 1, vp) | 310 | + | bind(t, d, p, 4, viewporter.WpViewporterInterface, 1, 1, vp) |
| 304 | view, e := vp.GetViewport(surface) | 311 | view, e := vp.GetViewport(surface) | ||
| 305 | noerr(t, e) | 312 | noerr(t, e) | ||
| 306 | request(t, p) | 313 | request(t, p) | ||
| @@ -309,7 +316,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 309 | noerr(t, vp.Destroy()) | 316 | noerr(t, vp.Destroy()) | ||
| 310 | request(t, p) | 317 | request(t, p) | ||
| 311 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | 318 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | ||
| 312 | - | bind(t, d, p, 5, fractionalscale.WpFractionalScaleManagerInterface, 1, fs) | 319 | + | bind(t, d, p, 5, fractionalscale.WpFractionalScaleManagerInterface, 1, 1, fs) |
| 313 | scale, e := fs.GetFractionalScale(surface) | 320 | scale, e := fs.GetFractionalScale(surface) | ||
| 314 | noerr(t, e) | 321 | noerr(t, e) | ||
| 315 | request(t, p) | 322 | request(t, p) | ||
| @@ -325,7 +332,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 325 | noerr(t, fs.Destroy()) | 332 | noerr(t, fs.Destroy()) | ||
| 326 | request(t, p) | 333 | request(t, p) | ||
| 327 | ti := textinput.NewTextInputManagerV3(d.Context()) | 334 | ti := textinput.NewTextInputManagerV3(d.Context()) | ||
| 328 | - | bind(t, d, p, 6, textinput.TextInputManagerV3Interface, 1, ti) | 335 | + | bind(t, d, p, 6, textinput.TextInputManagerV3Interface, 2, 1, ti) |
| 329 | input, e := ti.GetTextInput(seat) | 336 | input, e := ti.GetTextInput(seat) | ||
| 330 | noerr(t, e) | 337 | noerr(t, e) | ||
| 331 | request(t, p) | 338 | request(t, p) | ||
| @@ -362,7 +369,7 @@ func TestExtensionsOverSocketpair(t *testing.T) { | |||||
| 362 | // Core data-device remains in protocol/core; verify its typed child and | 369 | // Core data-device remains in protocol/core; verify its typed child and | ||
| 363 | // both directions of clipboard descriptor ownership. | 370 | // both directions of clipboard descriptor ownership. | ||
| 364 | mgr := core.NewDataDeviceManager(d.Context()) | 371 | mgr := core.NewDataDeviceManager(d.Context()) | ||
| 365 | - | bind(t, d, p, 7, core.DataDeviceManagerInterface, 3, mgr) | 372 | + | bind(t, d, p, 7, core.DataDeviceManagerInterface, 3, 4, mgr) |
| 366 | device, e := mgr.GetDataDevice(seat) | 373 | device, e := mgr.GetDataDevice(seat) | ||
| 367 | noerr(t, e) | 374 | noerr(t, e) | ||
| 368 | request(t, p) | 375 | request(t, p) | ||
| @@ -442,7 +449,11 @@ func TestLiveFeedback(t *testing.T) { | |||||
| 442 | t.Fatal("dmabuf feedback requires v4") | 449 | t.Fatal("dmabuf feedback requires v4") | ||
| 443 | } | 450 | } | ||
| 444 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | 451 | dm := linuxdmabuf.NewLinuxDmabuf(d.Context()) | ||
| 445 | - | noerr(t, d.Registry().Bind(g.Name, iface, v, dm)) | 452 | + | negotiated, err := d.Registry().BindNegotiated(iface, 4, dm) |
| 453 | + | noerr(t, err) | |||
| 454 | + | if negotiated != v { | |||
| 455 | + | t.Fatalf("dmabuf negotiated=%d want=%d", negotiated, v) | |||
| 456 | + | } | |||
| 446 | fb, e := dm.GetDefaultFeedback() | 457 | fb, e := dm.GetDefaultFeedback() | ||
| 447 | noerr(t, e) | 458 | noerr(t, e) | ||
| 448 | done := false | 459 | done := false | ||
M protocol/headless_test.go +6 -19
| @@ -73,23 +73,12 @@ func TestHeadlessNeferWL(t *testing.T) { | |||
| 73 | 73 | stop := context.AfterFunc(ctx, func() { _ = d.Close() }) | |
| 74 | 74 | defer stop() | |
| 75 | 75 | noerr(t, d.Roundtrip()) | |
| 76 | - | require := func(iface string) wlturbo.Global { | |
| 77 | - | t.Helper() | |
| 78 | - | g, ok := d.Registry().FindGlobal(iface) | |
| 79 | - | if !ok { | |
| 80 | - | t.Fatalf("required global %s absent\n%s", iface, headlessLog(log)) | |
| 81 | - | } | |
| 82 | - | t.Logf("%s server=%d", iface, g.Version) | |
| 83 | - | return g | |
| 84 | - | } | |
| 85 | 76 | bind := func(iface string, supported uint32, proxy wlturbo.Proxy) uint32 { | |
| 86 | 77 | t.Helper() | |
| 87 | - | g := require(iface) | |
| 88 | - | v := g.Version | |
| 89 | - | if v > supported { | |
| 90 | - | v = supported | |
| 78 | + | v, err := d.Registry().BindNegotiated(iface, supported, proxy) | |
| 79 | + | if err != nil { | |
| 80 | + | t.Fatalf("bind %s: %v\n%s", iface, err, headlessLog(log)) | |
| 91 | 81 | } | |
| 92 | - | noerr(t, d.Registry().Bind(g.Name, iface, v, proxy)) | |
| 93 | 82 | t.Logf("%s negotiated=%d", iface, v) | |
| 94 | 83 | return v | |
| 95 | 84 | } | |
| @@ -109,12 +98,10 @@ func TestHeadlessNeferWL(t *testing.T) { | |||
| 109 | 98 | bind(viewporter.WpViewporterInterface, 1, vp) | |
| 110 | 99 | if g, ok := d.Registry().FindGlobal(fractionalscale.WpFractionalScaleManagerInterface); ok { | |
| 111 | 100 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | |
| 112 | - | v := g.Version | |
| 113 | - | if v > 1 { | |
| 114 | - | v = 1 | |
| 101 | + | v := bind(g.Interface, 1, fs) | |
| 102 | + | if v != 1 { | |
| 103 | + | t.Fatalf("fractional scale version %d", v) | |
| 115 | 104 | } | |
| 116 | - | noerr(t, d.Registry().Bind(g.Name, g.Interface, v, fs)) | |
| 117 | - | t.Logf("%s negotiated=%d", g.Interface, v) | |
| 118 | 105 | noerr(t, fs.Destroy()) | |
| 119 | 106 | } else { | |
| 120 | 107 | t.Log("fractional scale absent") | |
| @@ -73,23 +73,12 @@ func TestHeadlessNeferWL(t *testing.T) { | |||||
| 73 | stop := context.AfterFunc(ctx, func() { _ = d.Close() }) | 73 | stop := context.AfterFunc(ctx, func() { _ = d.Close() }) | ||
| 74 | defer stop() | 74 | defer stop() | ||
| 75 | noerr(t, d.Roundtrip()) | 75 | noerr(t, d.Roundtrip()) | ||
| 76 | - | require := func(iface string) wlturbo.Global { | |||
| 77 | - | t.Helper() | |||
| 78 | - | g, ok := d.Registry().FindGlobal(iface) | |||
| 79 | - | if !ok { | |||
| 80 | - | t.Fatalf("required global %s absent\n%s", iface, headlessLog(log)) | |||
| 81 | - | } | |||
| 82 | - | t.Logf("%s server=%d", iface, g.Version) | |||
| 83 | - | return g | |||
| 84 | - | } | |||
| 85 | bind := func(iface string, supported uint32, proxy wlturbo.Proxy) uint32 { | 76 | bind := func(iface string, supported uint32, proxy wlturbo.Proxy) uint32 { | ||
| 86 | t.Helper() | 77 | t.Helper() | ||
| 87 | - | g := require(iface) | 78 | + | v, err := d.Registry().BindNegotiated(iface, supported, proxy) |
| 88 | - | v := g.Version | 79 | + | if err != nil { |
| 89 | - | if v > supported { | 80 | + | t.Fatalf("bind %s: %v\n%s", iface, err, headlessLog(log)) |
| 90 | - | v = supported | |||
| 91 | } | 81 | } | ||
| 92 | - | noerr(t, d.Registry().Bind(g.Name, iface, v, proxy)) | |||
| 93 | t.Logf("%s negotiated=%d", iface, v) | 82 | t.Logf("%s negotiated=%d", iface, v) | ||
| 94 | return v | 83 | return v | ||
| 95 | } | 84 | } | ||
| @@ -109,12 +98,10 @@ func TestHeadlessNeferWL(t *testing.T) { | |||||
| 109 | bind(viewporter.WpViewporterInterface, 1, vp) | 98 | bind(viewporter.WpViewporterInterface, 1, vp) | ||
| 110 | if g, ok := d.Registry().FindGlobal(fractionalscale.WpFractionalScaleManagerInterface); ok { | 99 | if g, ok := d.Registry().FindGlobal(fractionalscale.WpFractionalScaleManagerInterface); ok { | ||
| 111 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | 100 | fs := fractionalscale.NewWpFractionalScaleManager(d.Context()) | ||
| 112 | - | v := g.Version | 101 | + | v := bind(g.Interface, 1, fs) |
| 113 | - | if v > 1 { | 102 | + | if v != 1 { |
| 114 | - | v = 1 | 103 | + | t.Fatalf("fractional scale version %d", v) |
| 115 | } | 104 | } | ||
| 116 | - | noerr(t, d.Registry().Bind(g.Name, g.Interface, v, fs)) | |||
| 117 | - | t.Logf("%s negotiated=%d", g.Interface, v) | |||
| 118 | noerr(t, fs.Destroy()) | 105 | noerr(t, fs.Destroy()) | ||
| 119 | } else { | 106 | } else { | ||
| 120 | t.Log("fractional scale absent") | 107 | t.Log("fractional scale absent") | ||
M protocol/negotiation_test.go +11 -18
| @@ -4,26 +4,13 @@ package protocol_test | |||
| 4 | 4 | ||
| 5 | 5 | import ( | |
| 6 | 6 | "encoding/binary" | |
| 7 | + | "errors" | |
| 8 | + | "testing" | |
| 9 | + | ||
| 7 | 10 | "github.com/bnema/wlturbo" | |
| 8 | 11 | "github.com/bnema/wlturbo/protocol/xdgshell" | |
| 9 | - | "testing" | |
| 10 | 12 | ) | |
| 11 | 13 | ||
| 12 | - | // bindAnnounced selects a supported version using the registry's announcement. | |
| 13 | - | func bindAnnounced(d *wlturbo.Display, iface string, supported uint32, proxy wlturbo.Proxy) (uint32, error) { | |
| 14 | - | g, ok := d.Registry().FindGlobal(iface) | |
| 15 | - | if !ok { | |
| 16 | - | return 0, nil | |
| 17 | - | } | |
| 18 | - | version := g.Version | |
| 19 | - | if version > supported { | |
| 20 | - | version = supported | |
| 21 | - | } | |
| 22 | - | if version == 0 { | |
| 23 | - | return 0, nil | |
| 24 | - | } | |
| 25 | - | return version, d.Registry().Bind(g.Name, iface, version, proxy) | |
| 26 | - | } | |
| 27 | 14 | func TestAnnouncedVersionNegotiation(t *testing.T) { | |
| 28 | 15 | c, p := pair(t) | |
| 29 | 16 | d, e := wlturbo.ConnectFromConn(c) | |
| @@ -31,9 +18,12 @@ func TestAnnouncedVersionNegotiation(t *testing.T) { | |||
| 31 | 18 | defer d.Close() | |
| 32 | 19 | request(t, p) | |
| 33 | 20 | iface := xdgshell.XdgWmBaseInterface | |
| 34 | - | if v, e := bindAnnounced(d, iface, 6, xdgshell.NewXdgWmBase(d.Context())); e != nil || v != 0 { | |
| 21 | + | if v, e := d.Registry().BindNegotiated(iface, 6, xdgshell.NewXdgWmBase(d.Context())); v != 0 || !errors.Is(e, wlturbo.ErrGlobalNotFound) { | |
| 35 | 22 | t.Fatalf("missing: %d %v", v, e) | |
| 36 | 23 | } | |
| 24 | + | if v, e := d.Registry().BindNegotiated(iface, 0, xdgshell.NewXdgWmBase(d.Context())); v != 0 || e == nil || errors.Is(e, wlturbo.ErrGlobalNotFound) { | |
| 25 | + | t.Fatalf("unsupported zero: %d %v", v, e) | |
| 26 | + | } | |
| 37 | 27 | for i, version := range []uint32{2, 9} { | |
| 38 | 28 | name := uint32(30 + i) | |
| 39 | 29 | announcement := append(msg(0, 0, name)[8:], str(iface)...) | |
| @@ -41,7 +31,10 @@ func TestAnnouncedVersionNegotiation(t *testing.T) { | |||
| 41 | 31 | send(t, p, payload(d.Registry().ID(), 0, announcement...)) | |
| 42 | 32 | noerr(t, d.Dispatch()) | |
| 43 | 33 | proxy := xdgshell.NewXdgWmBase(d.Context()) | |
| 44 | - | negotiated, e := bindAnnounced(d, iface, 6, proxy) | |
| 34 | + | if v, e := d.Registry().BindNegotiated(iface, 0, proxy); v != 0 || e == nil || errors.Is(e, wlturbo.ErrGlobalNotFound) { | |
| 35 | + | t.Fatalf("supported zero with global: %d %v", v, e) | |
| 36 | + | } | |
| 37 | + | negotiated, e := d.Registry().BindNegotiated(iface, 6, proxy) | |
| 45 | 38 | noerr(t, e) | |
| 46 | 39 | want := version | |
| 47 | 40 | if want > 6 { | |
| @@ -4,26 +4,13 @@ package protocol_test | |||||
| 4 | 4 | ||||
| 5 | import ( | 5 | import ( | ||
| 6 | "encoding/binary" | 6 | "encoding/binary" | ||
| 7 | + | "errors" | |||
| 8 | + | "testing" | |||
| 9 | + | ||||
| 7 | "github.com/bnema/wlturbo" | 10 | "github.com/bnema/wlturbo" | ||
| 8 | "github.com/bnema/wlturbo/protocol/xdgshell" | 11 | "github.com/bnema/wlturbo/protocol/xdgshell" | ||
| 9 | - | "testing" | |||
| 10 | ) | 12 | ) | ||
| 11 | 13 | ||||
| 12 | - | // bindAnnounced selects a supported version using the registry's announcement. | |||
| 13 | - | func bindAnnounced(d *wlturbo.Display, iface string, supported uint32, proxy wlturbo.Proxy) (uint32, error) { | |||
| 14 | - | g, ok := d.Registry().FindGlobal(iface) | |||
| 15 | - | if !ok { | |||
| 16 | - | return 0, nil | |||
| 17 | - | } | |||
| 18 | - | version := g.Version | |||
| 19 | - | if version > supported { | |||
| 20 | - | version = supported | |||
| 21 | - | } | |||
| 22 | - | if version == 0 { | |||
| 23 | - | return 0, nil | |||
| 24 | - | } | |||
| 25 | - | return version, d.Registry().Bind(g.Name, iface, version, proxy) | |||
| 26 | - | } | |||
| 27 | func TestAnnouncedVersionNegotiation(t *testing.T) { | 14 | func TestAnnouncedVersionNegotiation(t *testing.T) { | ||
| 28 | c, p := pair(t) | 15 | c, p := pair(t) | ||
| 29 | d, e := wlturbo.ConnectFromConn(c) | 16 | d, e := wlturbo.ConnectFromConn(c) | ||
| @@ -31,9 +18,12 @@ func TestAnnouncedVersionNegotiation(t *testing.T) { | |||||
| 31 | defer d.Close() | 18 | defer d.Close() | ||
| 32 | request(t, p) | 19 | request(t, p) | ||
| 33 | iface := xdgshell.XdgWmBaseInterface | 20 | iface := xdgshell.XdgWmBaseInterface | ||
| 34 | - | if v, e := bindAnnounced(d, iface, 6, xdgshell.NewXdgWmBase(d.Context())); e != nil || v != 0 { | 21 | + | if v, e := d.Registry().BindNegotiated(iface, 6, xdgshell.NewXdgWmBase(d.Context())); v != 0 || !errors.Is(e, wlturbo.ErrGlobalNotFound) { |
| 35 | t.Fatalf("missing: %d %v", v, e) | 22 | t.Fatalf("missing: %d %v", v, e) | ||
| 36 | } | 23 | } | ||
| 24 | + | if v, e := d.Registry().BindNegotiated(iface, 0, xdgshell.NewXdgWmBase(d.Context())); v != 0 || e == nil || errors.Is(e, wlturbo.ErrGlobalNotFound) { | |||
| 25 | + | t.Fatalf("unsupported zero: %d %v", v, e) | |||
| 26 | + | } | |||
| 37 | for i, version := range []uint32{2, 9} { | 27 | for i, version := range []uint32{2, 9} { | ||
| 38 | name := uint32(30 + i) | 28 | name := uint32(30 + i) | ||
| 39 | announcement := append(msg(0, 0, name)[8:], str(iface)...) | 29 | announcement := append(msg(0, 0, name)[8:], str(iface)...) | ||
| @@ -41,7 +31,10 @@ func TestAnnouncedVersionNegotiation(t *testing.T) { | |||||
| 41 | send(t, p, payload(d.Registry().ID(), 0, announcement...)) | 31 | send(t, p, payload(d.Registry().ID(), 0, announcement...)) | ||
| 42 | noerr(t, d.Dispatch()) | 32 | noerr(t, d.Dispatch()) | ||
| 43 | proxy := xdgshell.NewXdgWmBase(d.Context()) | 33 | proxy := xdgshell.NewXdgWmBase(d.Context()) | ||
| 44 | - | negotiated, e := bindAnnounced(d, iface, 6, proxy) | 34 | + | if v, e := d.Registry().BindNegotiated(iface, 0, proxy); v != 0 || e == nil || errors.Is(e, wlturbo.ErrGlobalNotFound) { |
| 35 | + | t.Fatalf("supported zero with global: %d %v", v, e) | |||
| 36 | + | } | |||
| 37 | + | negotiated, e := d.Registry().BindNegotiated(iface, 6, proxy) | |||
| 45 | noerr(t, e) | 38 | noerr(t, e) | ||
| 46 | want := version | 39 | want := version | ||
| 47 | if want > 6 { | 40 | if want > 6 { | ||