merge: removable globals
3 files · +61 -3
M internal/e2e/feasibility_test.go +32 -0
| @@ -321,3 +321,35 @@ func TestClientPID(t *testing.T) { | |||
| 321 | 321 | t.Fatalf("pid %d, want %d", got, os.Getpid()) | |
| 322 | 322 | } | |
| 323 | 323 | } | |
| 324 | + | ||
| 325 | + | // A removed global disappears from the registry of new clients. | |
| 326 | + | func TestGlobalRemove(t *testing.T) { | |
| 327 | + | socket, d, _ := startServer(t) | |
| 328 | + | var g *server.Global | |
| 329 | + | added := make(chan error, 1) | |
| 330 | + | if !d.Do(func() { | |
| 331 | + | var err error | |
| 332 | + | g, err = d.AddGlobal(wayland.OutputInterface, 4, func(server.Client, uint32, uint32) {}) | |
| 333 | + | added <- err | |
| 334 | + | }) { | |
| 335 | + | t.Fatal("display stopped") | |
| 336 | + | } | |
| 337 | + | must(t, <-added) | |
| 338 | + | has := func() bool { | |
| 339 | + | c, err := wlturbo.Connect(socket) | |
| 340 | + | must(t, err) | |
| 341 | + | defer c.Close() | |
| 342 | + | must(t, c.Roundtrip()) | |
| 343 | + | _, ok := c.Registry().FindGlobal("wl_output") | |
| 344 | + | return ok | |
| 345 | + | } | |
| 346 | + | if !has() { | |
| 347 | + | t.Fatal("wl_output not advertised") | |
| 348 | + | } | |
| 349 | + | if !d.Do(func() { g.Remove(); g.Remove() }) { | |
| 350 | + | t.Fatal("display stopped") | |
| 351 | + | } | |
| 352 | + | if has() { | |
| 353 | + | t.Fatal("wl_output still advertised after Remove") | |
| 354 | + | } | |
| 355 | + | } | |
| @@ -321,3 +321,35 @@ func TestClientPID(t *testing.T) { | |||||
| 321 | t.Fatalf("pid %d, want %d", got, os.Getpid()) | 321 | t.Fatalf("pid %d, want %d", got, os.Getpid()) | ||
| 322 | } | 322 | } | ||
| 323 | } | 323 | } | ||
| 324 | + | ||||
| 325 | + | // A removed global disappears from the registry of new clients. | |||
| 326 | + | func TestGlobalRemove(t *testing.T) { | |||
| 327 | + | socket, d, _ := startServer(t) | |||
| 328 | + | var g *server.Global | |||
| 329 | + | added := make(chan error, 1) | |||
| 330 | + | if !d.Do(func() { | |||
| 331 | + | var err error | |||
| 332 | + | g, err = d.AddGlobal(wayland.OutputInterface, 4, func(server.Client, uint32, uint32) {}) | |||
| 333 | + | added <- err | |||
| 334 | + | }) { | |||
| 335 | + | t.Fatal("display stopped") | |||
| 336 | + | } | |||
| 337 | + | must(t, <-added) | |||
| 338 | + | has := func() bool { | |||
| 339 | + | c, err := wlturbo.Connect(socket) | |||
| 340 | + | must(t, err) | |||
| 341 | + | defer c.Close() | |||
| 342 | + | must(t, c.Roundtrip()) | |||
| 343 | + | _, ok := c.Registry().FindGlobal("wl_output") | |||
| 344 | + | return ok | |||
| 345 | + | } | |||
| 346 | + | if !has() { | |||
| 347 | + | t.Fatal("wl_output not advertised") | |||
| 348 | + | } | |||
| 349 | + | if !d.Do(func() { g.Remove(); g.Remove() }) { | |||
| 350 | + | t.Fatal("display stopped") | |||
| 351 | + | } | |||
| 352 | + | if has() { | |||
| 353 | + | t.Fatal("wl_output still advertised after Remove") | |||
| 354 | + | } | |||
| 355 | + | } | |||
M server/display.go +27 -3
| @@ -89,14 +89,38 @@ func (d *Display) AddSocketFD(fd int) error { | |||
| 89 | 89 | } | |
| 90 | 90 | ||
| 91 | 91 | func (d *Display) CreateGlobal(iface *Interface, version int32, bindFn BindFunc) error { | |
| 92 | + | _, err := d.AddGlobal(iface, version, bindFn) | |
| 93 | + | return err | |
| 94 | + | } | |
| 95 | + | ||
| 96 | + | // Global is a global that can be withdrawn, such as a wl_output on hotplug. | |
| 97 | + | type Global struct{ c uintptr } | |
| 98 | + | ||
| 99 | + | // AddGlobal advertises a global and returns a handle to remove it later. | |
| 100 | + | func (d *Display) AddGlobal(iface *Interface, version int32, bindFn BindFunc) (*Global, error) { | |
| 92 | 101 | live.nextBind++ | |
| 93 | 102 | token := live.nextBind | |
| 94 | 103 | live.binds[token] = bindFn | |
| 95 | - | if wlGlobalCreate(d.c, iface.c, version, token, cbBind) == 0 { | |
| 104 | + | c := wlGlobalCreate(d.c, iface.c, version, token, cbBind) | |
| 105 | + | if c == 0 { | |
| 96 | 106 | delete(live.binds, token) | |
| 97 | - | return fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name) | |
| 107 | + | return nil, fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name) | |
| 98 | 108 | } | |
| 99 | - | return nil | |
| 109 | + | return &Global{c: c}, nil | |
| 110 | + | } | |
| 111 | + | ||
| 112 | + | // Remove withdraws the global: clients get wl_registry.global_remove. | |
| 113 | + | // Existing resources stay valid. A client that binds before it sees the | |
| 114 | + | // removal still gets its bind function called, so it always receives a | |
| 115 | + | // resource (the protocol allows binding a just-removed global); the bind | |
| 116 | + | // function should then send inert state. The global is freed with the | |
| 117 | + | // display. Display goroutine only; idempotent. | |
| 118 | + | func (g *Global) Remove() { | |
| 119 | + | if g == nil || g.c == 0 { | |
| 120 | + | return | |
| 121 | + | } | |
| 122 | + | wlGlobalRemove(g.c) | |
| 123 | + | g.c = 0 | |
| 100 | 124 | } | |
| 101 | 125 | ||
| 102 | 126 | // Close destroys a display that has not started Run. It is a no-op once Run | |
| @@ -89,14 +89,38 @@ func (d *Display) AddSocketFD(fd int) error { | |||||
| 89 | } | 89 | } | ||
| 90 | 90 | ||||
| 91 | func (d *Display) CreateGlobal(iface *Interface, version int32, bindFn BindFunc) error { | 91 | func (d *Display) CreateGlobal(iface *Interface, version int32, bindFn BindFunc) error { | ||
| 92 | + | _, err := d.AddGlobal(iface, version, bindFn) | |||
| 93 | + | return err | |||
| 94 | + | } | |||
| 95 | + | ||||
| 96 | + | // Global is a global that can be withdrawn, such as a wl_output on hotplug. | |||
| 97 | + | type Global struct{ c uintptr } | |||
| 98 | + | ||||
| 99 | + | // AddGlobal advertises a global and returns a handle to remove it later. | |||
| 100 | + | func (d *Display) AddGlobal(iface *Interface, version int32, bindFn BindFunc) (*Global, error) { | |||
| 92 | live.nextBind++ | 101 | live.nextBind++ | ||
| 93 | token := live.nextBind | 102 | token := live.nextBind | ||
| 94 | live.binds[token] = bindFn | 103 | live.binds[token] = bindFn | ||
| 95 | - | if wlGlobalCreate(d.c, iface.c, version, token, cbBind) == 0 { | 104 | + | c := wlGlobalCreate(d.c, iface.c, version, token, cbBind) |
| 105 | + | if c == 0 { | |||
| 96 | delete(live.binds, token) | 106 | delete(live.binds, token) | ||
| 97 | - | return fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name) | 107 | + | return nil, fmt.Errorf("purego-libwayland: wl_global_create(%s) failed", iface.Name) |
| 98 | } | 108 | } | ||
| 99 | - | return nil | 109 | + | return &Global{c: c}, nil |
| 110 | + | } | |||
| 111 | + | ||||
| 112 | + | // Remove withdraws the global: clients get wl_registry.global_remove. | |||
| 113 | + | // Existing resources stay valid. A client that binds before it sees the | |||
| 114 | + | // removal still gets its bind function called, so it always receives a | |||
| 115 | + | // resource (the protocol allows binding a just-removed global); the bind | |||
| 116 | + | // function should then send inert state. The global is freed with the | |||
| 117 | + | // display. Display goroutine only; idempotent. | |||
| 118 | + | func (g *Global) Remove() { | |||
| 119 | + | if g == nil || g.c == 0 { | |||
| 120 | + | return | |||
| 121 | + | } | |||
| 122 | + | wlGlobalRemove(g.c) | |||
| 123 | + | g.c = 0 | |||
| 100 | } | 124 | } | ||
| 101 | 125 | ||||
| 102 | // Close destroys a display that has not started Run. It is a no-op once Run | 126 | // Close destroys a display that has not started Run. It is a no-op once Run | ||
M server/lib.go +2 -0
| @@ -21,6 +21,7 @@ var ( | |||
| 21 | 21 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | |
| 22 | 22 | wlEventSourceRemove func(source uintptr) int32 | |
| 23 | 23 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | |
| 24 | + | wlGlobalRemove func(global uintptr) | |
| 24 | 25 | wlResourceCreate func(client, iface uintptr, version int32, id uint32) uintptr | |
| 25 | 26 | wlResourceSetDispatcher func(resource, dispatcher, impl, data, destroy uintptr) | |
| 26 | 27 | wlResourcePostEventArr func(resource uintptr, opcode uint32, args unsafe.Pointer) | |
| @@ -68,6 +69,7 @@ func load() error { | |||
| 68 | 69 | reg(&wlEventLoopAddFD, "wl_event_loop_add_fd") | |
| 69 | 70 | reg(&wlEventSourceRemove, "wl_event_source_remove") | |
| 70 | 71 | reg(&wlGlobalCreate, "wl_global_create") | |
| 72 | + | reg(&wlGlobalRemove, "wl_global_remove") | |
| 71 | 73 | reg(&wlResourceCreate, "wl_resource_create") | |
| 72 | 74 | reg(&wlResourceSetDispatcher, "wl_resource_set_dispatcher") | |
| 73 | 75 | reg(&wlResourcePostEventArr, "wl_resource_post_event_array") | |
| @@ -21,6 +21,7 @@ var ( | |||||
| 21 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | 21 | wlEventLoopAddFD func(loop uintptr, fd int32, mask uint32, fn, data uintptr) uintptr | ||
| 22 | wlEventSourceRemove func(source uintptr) int32 | 22 | wlEventSourceRemove func(source uintptr) int32 | ||
| 23 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | 23 | wlGlobalCreate func(display, iface uintptr, version int32, data uintptr, bind uintptr) uintptr | ||
| 24 | + | wlGlobalRemove func(global uintptr) | |||
| 24 | wlResourceCreate func(client, iface uintptr, version int32, id uint32) uintptr | 25 | wlResourceCreate func(client, iface uintptr, version int32, id uint32) uintptr | ||
| 25 | wlResourceSetDispatcher func(resource, dispatcher, impl, data, destroy uintptr) | 26 | wlResourceSetDispatcher func(resource, dispatcher, impl, data, destroy uintptr) | ||
| 26 | wlResourcePostEventArr func(resource uintptr, opcode uint32, args unsafe.Pointer) | 27 | wlResourcePostEventArr func(resource uintptr, opcode uint32, args unsafe.Pointer) | ||
| @@ -68,6 +69,7 @@ func load() error { | |||||
| 68 | reg(&wlEventLoopAddFD, "wl_event_loop_add_fd") | 69 | reg(&wlEventLoopAddFD, "wl_event_loop_add_fd") | ||
| 69 | reg(&wlEventSourceRemove, "wl_event_source_remove") | 70 | reg(&wlEventSourceRemove, "wl_event_source_remove") | ||
| 70 | reg(&wlGlobalCreate, "wl_global_create") | 71 | reg(&wlGlobalCreate, "wl_global_create") | ||
| 72 | + | reg(&wlGlobalRemove, "wl_global_remove") | |||
| 71 | reg(&wlResourceCreate, "wl_resource_create") | 73 | reg(&wlResourceCreate, "wl_resource_create") | ||
| 72 | reg(&wlResourceSetDispatcher, "wl_resource_set_dispatcher") | 74 | reg(&wlResourceSetDispatcher, "wl_resource_set_dispatcher") | ||
| 73 | reg(&wlResourcePostEventArr, "wl_resource_post_event_array") | 75 | reg(&wlResourcePostEventArr, "wl_resource_post_event_array") | ||