feat(xwm): host timer seam After for selection timeouts
- commit
- ad55fe80087e319683c0ab307ae2f182f145c3cb
- author
- brice · · 2026-10-22 16:15 UTC
- committer
- brice · · 2026-10-22 17:55 UTC
- parent
- 41edcc9
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +79 -1
M internal/adapters/wayland/x11window.go +7 -0
| @@ -1,6 +1,8 @@ | |||
| 1 | 1 | package wayland | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "time" | |
| 5 | + | ||
| 4 | 6 | "github.com/bnema/go-wayland-bindings/server/wayland" | |
| 5 | 7 | "github.com/bnema/go-wayland-bindings/server/xwaylandshell" | |
| 6 | 8 | "github.com/bnema/neferwl/internal/adapters/wayland/xwm" | |
| @@ -377,6 +379,11 @@ func (h hostImpl) SelectionOwned(kind int, mimes []string) { | |||
| 377 | 379 | h.s.log.Debug().Int("kind", kind).Int("mimes", len(mimes)).Msg("X selection owner changed") | |
| 378 | 380 | } | |
| 379 | 381 | ||
| 382 | + | func (h hostImpl) After(d time.Duration, fn func()) func() { | |
| 383 | + | t := h.s.clock.AfterFunc(d, func() { h.s.display.Post(fn) }) | |
| 384 | + | return func() { t.Stop() } | |
| 385 | + | } | |
| 386 | + | ||
| 380 | 387 | func (h hostImpl) OverrideGone(slot xwm.Slot) { | |
| 381 | 388 | s := h.s | |
| 382 | 389 | win := s.x11Slots[slot] | |
| @@ -1,6 +1,8 @@ | |||||
| 1 | package wayland | 1 | package wayland | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "time" | |||
| 5 | + | ||||
| 4 | "github.com/bnema/go-wayland-bindings/server/wayland" | 6 | "github.com/bnema/go-wayland-bindings/server/wayland" | ||
| 5 | "github.com/bnema/go-wayland-bindings/server/xwaylandshell" | 7 | "github.com/bnema/go-wayland-bindings/server/xwaylandshell" | ||
| 6 | "github.com/bnema/neferwl/internal/adapters/wayland/xwm" | 8 | "github.com/bnema/neferwl/internal/adapters/wayland/xwm" | ||
| @@ -377,6 +379,11 @@ func (h hostImpl) SelectionOwned(kind int, mimes []string) { | |||||
| 377 | h.s.log.Debug().Int("kind", kind).Int("mimes", len(mimes)).Msg("X selection owner changed") | 379 | h.s.log.Debug().Int("kind", kind).Int("mimes", len(mimes)).Msg("X selection owner changed") | ||
| 378 | } | 380 | } | ||
| 379 | 381 | ||||
| 382 | + | func (h hostImpl) After(d time.Duration, fn func()) func() { | |||
| 383 | + | t := h.s.clock.AfterFunc(d, func() { h.s.display.Post(fn) }) | |||
| 384 | + | return func() { t.Stop() } | |||
| 385 | + | } | |||
| 386 | + | ||||
| 380 | func (h hostImpl) OverrideGone(slot xwm.Slot) { | 387 | func (h hostImpl) OverrideGone(slot xwm.Slot) { | ||
| 381 | s := h.s | 388 | s := h.s | ||
| 382 | win := s.x11Slots[slot] | 389 | win := s.x11Slots[slot] | ||
M internal/adapters/wayland/xwm/host.go +11 -1
| @@ -1,6 +1,10 @@ | |||
| 1 | 1 | package xwm | |
| 2 | 2 | ||
| 3 | - | import "github.com/bnema/neferwl/internal/ports" | |
| 3 | + | import ( | |
| 4 | + | "time" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/neferwl/internal/ports" | |
| 7 | + | ) | |
| 4 | 8 | ||
| 5 | 9 | // Slot is the XWM's window slot: table index + 1, stable until the window is | |
| 6 | 10 | // destroyed. 0 means none. | |
| @@ -58,6 +62,12 @@ type host interface { | |||
| 58 | 62 | // offers these MIME types, or none does any more (mimes nil). mimes is | |
| 59 | 63 | // scratch: the host copies it. | |
| 60 | 64 | SelectionOwned(kind int, mimes []string) | |
| 65 | + | ||
| 66 | + | // After runs fn on the display goroutine once d has elapsed, unless the | |
| 67 | + | // returned cancel ran first. cancel is idempotent and display goroutine | |
| 68 | + | // only; fn may still run once after cancel when it was already queued, | |
| 69 | + | // so callers guard with their own generation. | |
| 70 | + | After(d time.Duration, fn func()) (cancel func()) | |
| 61 | 71 | } | |
| 62 | 72 | ||
| 63 | 73 | // fdSource is the display's event source for the X socket (*server.Source). | |
| @@ -1,6 +1,10 @@ | |||||
| 1 | package xwm | 1 | package xwm | ||
| 2 | 2 | ||||
| 3 | - | import "github.com/bnema/neferwl/internal/ports" | 3 | + | import ( |
| 4 | + | "time" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/neferwl/internal/ports" | |||
| 7 | + | ) | |||
| 4 | 8 | ||||
| 5 | // Slot is the XWM's window slot: table index + 1, stable until the window is | 9 | // Slot is the XWM's window slot: table index + 1, stable until the window is | ||
| 6 | // destroyed. 0 means none. | 10 | // destroyed. 0 means none. | ||
| @@ -58,6 +62,12 @@ type host interface { | |||||
| 58 | // offers these MIME types, or none does any more (mimes nil). mimes is | 62 | // offers these MIME types, or none does any more (mimes nil). mimes is | ||
| 59 | // scratch: the host copies it. | 63 | // scratch: the host copies it. | ||
| 60 | SelectionOwned(kind int, mimes []string) | 64 | SelectionOwned(kind int, mimes []string) | ||
| 65 | + | ||||
| 66 | + | // After runs fn on the display goroutine once d has elapsed, unless the | |||
| 67 | + | // returned cancel ran first. cancel is idempotent and display goroutine | |||
| 68 | + | // only; fn may still run once after cancel when it was already queued, | |||
| 69 | + | // so callers guard with their own generation. | |||
| 70 | + | After(d time.Duration, fn func()) (cancel func()) | |||
| 61 | } | 71 | } | ||
| 62 | 72 | ||||
| 63 | // fdSource is the display's event source for the X socket (*server.Source). | 73 | // fdSource is the display's event source for the X socket (*server.Source). | ||
M internal/adapters/wayland/xwm/host_mock_test.go +61 -0
| @@ -5,6 +5,8 @@ | |||
| 5 | 5 | package xwm | |
| 6 | 6 | ||
| 7 | 7 | import ( | |
| 8 | + | "time" | |
| 9 | + | ||
| 8 | 10 | "github.com/bnema/neferwl/internal/ports" | |
| 9 | 11 | mock "github.com/stretchr/testify/mock" | |
| 10 | 12 | ) | |
| @@ -45,6 +47,65 @@ func (_m *mockhost) EXPECT() *mockhost_Expecter { | |||
| 45 | 47 | return &mockhost_Expecter{mock: &_m.Mock} | |
| 46 | 48 | } | |
| 47 | 49 | ||
| 50 | + | // After provides a mock function for the type mockhost | |
| 51 | + | func (_mock *mockhost) After(d time.Duration, fn func()) func() { | |
| 52 | + | ret := _mock.Called(d, fn) | |
| 53 | + | ||
| 54 | + | if len(ret) == 0 { | |
| 55 | + | panic("no return value specified for After") | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | var r0 func() | |
| 59 | + | if returnFunc, ok := ret.Get(0).(func(time.Duration, func()) func()); ok { | |
| 60 | + | r0 = returnFunc(d, fn) | |
| 61 | + | } else { | |
| 62 | + | if ret.Get(0) != nil { | |
| 63 | + | r0 = ret.Get(0).(func()) | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | return r0 | |
| 67 | + | } | |
| 68 | + | ||
| 69 | + | // mockhost_After_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'After' | |
| 70 | + | type mockhost_After_Call struct { | |
| 71 | + | *mock.Call | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // After is a helper method to define mock.On call | |
| 75 | + | // - d time.Duration | |
| 76 | + | // - fn func() | |
| 77 | + | func (_e *mockhost_Expecter) After(d any, fn any) *mockhost_After_Call { | |
| 78 | + | return &mockhost_After_Call{Call: _e.mock.On("After", d, fn)} | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | func (_c *mockhost_After_Call) Run(run func(d time.Duration, fn func())) *mockhost_After_Call { | |
| 82 | + | _c.Call.Run(func(args mock.Arguments) { | |
| 83 | + | var arg0 time.Duration | |
| 84 | + | if args[0] != nil { | |
| 85 | + | arg0 = args[0].(time.Duration) | |
| 86 | + | } | |
| 87 | + | var arg1 func() | |
| 88 | + | if args[1] != nil { | |
| 89 | + | arg1 = args[1].(func()) | |
| 90 | + | } | |
| 91 | + | run( | |
| 92 | + | arg0, | |
| 93 | + | arg1, | |
| 94 | + | ) | |
| 95 | + | }) | |
| 96 | + | return _c | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | func (_c *mockhost_After_Call) Return(cancel func()) *mockhost_After_Call { | |
| 100 | + | _c.Call.Return(cancel) | |
| 101 | + | return _c | |
| 102 | + | } | |
| 103 | + | ||
| 104 | + | func (_c *mockhost_After_Call) RunAndReturn(run func(d time.Duration, fn func()) func()) *mockhost_After_Call { | |
| 105 | + | _c.Call.Return(run) | |
| 106 | + | return _c | |
| 107 | + | } | |
| 108 | + | ||
| 48 | 109 | // AppID provides a mock function for the type mockhost | |
| 49 | 110 | func (_mock *mockhost) AppID(slot Slot, appID string) { | |
| 50 | 111 | _mock.Called(slot, appID) | |
| @@ -5,6 +5,8 @@ | |||||
| 5 | package xwm | 5 | package xwm | ||
| 6 | 6 | ||||
| 7 | import ( | 7 | import ( | ||
| 8 | + | "time" | |||
| 9 | + | ||||
| 8 | "github.com/bnema/neferwl/internal/ports" | 10 | "github.com/bnema/neferwl/internal/ports" | ||
| 9 | mock "github.com/stretchr/testify/mock" | 11 | mock "github.com/stretchr/testify/mock" | ||
| 10 | ) | 12 | ) | ||
| @@ -45,6 +47,65 @@ func (_m *mockhost) EXPECT() *mockhost_Expecter { | |||||
| 45 | return &mockhost_Expecter{mock: &_m.Mock} | 47 | return &mockhost_Expecter{mock: &_m.Mock} | ||
| 46 | } | 48 | } | ||
| 47 | 49 | ||||
| 50 | + | // After provides a mock function for the type mockhost | |||
| 51 | + | func (_mock *mockhost) After(d time.Duration, fn func()) func() { | |||
| 52 | + | ret := _mock.Called(d, fn) | |||
| 53 | + | ||||
| 54 | + | if len(ret) == 0 { | |||
| 55 | + | panic("no return value specified for After") | |||
| 56 | + | } | |||
| 57 | + | ||||
| 58 | + | var r0 func() | |||
| 59 | + | if returnFunc, ok := ret.Get(0).(func(time.Duration, func()) func()); ok { | |||
| 60 | + | r0 = returnFunc(d, fn) | |||
| 61 | + | } else { | |||
| 62 | + | if ret.Get(0) != nil { | |||
| 63 | + | r0 = ret.Get(0).(func()) | |||
| 64 | + | } | |||
| 65 | + | } | |||
| 66 | + | return r0 | |||
| 67 | + | } | |||
| 68 | + | ||||
| 69 | + | // mockhost_After_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'After' | |||
| 70 | + | type mockhost_After_Call struct { | |||
| 71 | + | *mock.Call | |||
| 72 | + | } | |||
| 73 | + | ||||
| 74 | + | // After is a helper method to define mock.On call | |||
| 75 | + | // - d time.Duration | |||
| 76 | + | // - fn func() | |||
| 77 | + | func (_e *mockhost_Expecter) After(d any, fn any) *mockhost_After_Call { | |||
| 78 | + | return &mockhost_After_Call{Call: _e.mock.On("After", d, fn)} | |||
| 79 | + | } | |||
| 80 | + | ||||
| 81 | + | func (_c *mockhost_After_Call) Run(run func(d time.Duration, fn func())) *mockhost_After_Call { | |||
| 82 | + | _c.Call.Run(func(args mock.Arguments) { | |||
| 83 | + | var arg0 time.Duration | |||
| 84 | + | if args[0] != nil { | |||
| 85 | + | arg0 = args[0].(time.Duration) | |||
| 86 | + | } | |||
| 87 | + | var arg1 func() | |||
| 88 | + | if args[1] != nil { | |||
| 89 | + | arg1 = args[1].(func()) | |||
| 90 | + | } | |||
| 91 | + | run( | |||
| 92 | + | arg0, | |||
| 93 | + | arg1, | |||
| 94 | + | ) | |||
| 95 | + | }) | |||
| 96 | + | return _c | |||
| 97 | + | } | |||
| 98 | + | ||||
| 99 | + | func (_c *mockhost_After_Call) Return(cancel func()) *mockhost_After_Call { | |||
| 100 | + | _c.Call.Return(cancel) | |||
| 101 | + | return _c | |||
| 102 | + | } | |||
| 103 | + | ||||
| 104 | + | func (_c *mockhost_After_Call) RunAndReturn(run func(d time.Duration, fn func()) func()) *mockhost_After_Call { | |||
| 105 | + | _c.Call.Return(run) | |||
| 106 | + | return _c | |||
| 107 | + | } | |||
| 108 | + | ||||
| 48 | // AppID provides a mock function for the type mockhost | 109 | // AppID provides a mock function for the type mockhost | ||
| 49 | func (_mock *mockhost) AppID(slot Slot, appID string) { | 110 | func (_mock *mockhost) AppID(slot Slot, appID string) { | ||
| 50 | _mock.Called(slot, appID) | 111 | _mock.Called(slot, appID) | ||