detached 7c37286

feat(present): add Surface.DestroyTimeline

commit
7c372866b041246e4970efd36727716b88a3705c
author
brice · · 2026-10-01 19:21 UTC
parent
49445e1
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
2 files · +44 -0
M present.go +16 -0
@@ -124,6 +124,22 @@ func (s *Surface) ImportTimeline(id uint64, fd int) error {
124124 return nil
125125 }
126126
127+// DestroyTimeline destroys an imported timeline object. The id is the one given
128+// to [Surface.ImportTimeline]; it may be imported again afterwards. Do not
129+// destroy a timeline that a pending commit still references. Surface.Close
130+// destroys every remaining timeline.
131+func (s *Surface) DestroyTimeline(id uint64) error {
132+ t, ok := s.timelines[id]
133+ if !ok {
134+ return fmt.Errorf("neferclient: unknown timeline %d", id)
135+ }
136+ delete(s.timelines, id)
137+ if err := t.Destroy(); err != nil {
138+ return fmt.Errorf("neferclient: destroy timeline %d: %w", id, err)
139+ }
140+ return nil
141+}
142+
127143 // Present commits an imported buffer. It refuses before the first configure,
128144 // before the first complete dmabuf feedback and while the previous frame
129145 // callback is pending.
@@ -124,6 +124,22 @@ func (s *Surface) ImportTimeline(id uint64, fd int) error {
124 return nil124 return nil
125 }125 }
126 126
127+// DestroyTimeline destroys an imported timeline object. The id is the one given
128+// to [Surface.ImportTimeline]; it may be imported again afterwards. Do not
129+// destroy a timeline that a pending commit still references. Surface.Close
130+// destroys every remaining timeline.
131+func (s *Surface) DestroyTimeline(id uint64) error {
132+ t, ok := s.timelines[id]
133+ if !ok {
134+ return fmt.Errorf("neferclient: unknown timeline %d", id)
135+ }
136+ delete(s.timelines, id)
137+ if err := t.Destroy(); err != nil {
138+ return fmt.Errorf("neferclient: destroy timeline %d: %w", id, err)
139+ }
140+ return nil
141+}
142+
127 // Present commits an imported buffer. It refuses before the first configure,143 // Present commits an imported buffer. It refuses before the first configure,
128 // before the first complete dmabuf feedback and while the previous frame144 // before the first complete dmabuf feedback and while the previous frame
129 // callback is pending.145 // callback is pending.
M surface_wire_test.go +28 -0
@@ -58,6 +58,34 @@ func TestImportErrorPathsCloseDupsOnce(t *testing.T) {
5858 require.Equal(t, before, openFDs(t))
5959 }
6060
61+// DestroyTimeline sends wp_linux_drm_syncobj_timeline_v1.destroy (opcode 0) for
62+// the imported timeline, refuses an unknown id and allows importing it again.
63+func TestDestroyTimeline(t *testing.T) {
64+ c, srv := connectWire(t, surfaceGlobals, surfaceOutputs)
65+ s, err := c.NewToplevel("tl", 64, 48)
66+ require.NoError(t, err)
67+ memfd, err := unix.MemfdCreate("tl", unix.MFD_CLOEXEC)
68+ require.NoError(t, err)
69+ defer unix.Close(memfd)
70+ require.NoError(t, s.ImportTimeline(7, memfd))
71+ require.NoError(t, c.Roundtrip())
72+
73+ var destroys atomic.Int32
74+ srv.mu.Lock()
75+ srv.hook = func(_ uint32, op uint16, _ []byte) {
76+ if op == 0 {
77+ destroys.Add(1)
78+ }
79+ }
80+ srv.mu.Unlock()
81+ require.NoError(t, s.DestroyTimeline(7))
82+ require.NoError(t, c.Roundtrip())
83+ require.Equal(t, int32(1), destroys.Load(), "one destroy request")
84+ require.ErrorContains(t, s.DestroyTimeline(7), "unknown timeline 7")
85+ require.ErrorContains(t, s.DestroyTimeline(99), "unknown timeline 99")
86+ require.NoError(t, s.ImportTimeline(7, memfd), "the id is free again")
87+}
88+
6189 // requestLog counts the commits the wire peer sees.
6290 type requestLog struct{ commits atomic.Int32 }
6391
@@ -58,6 +58,34 @@ func TestImportErrorPathsCloseDupsOnce(t *testing.T) {
58 require.Equal(t, before, openFDs(t))58 require.Equal(t, before, openFDs(t))
59 }59 }
60 60
61+// DestroyTimeline sends wp_linux_drm_syncobj_timeline_v1.destroy (opcode 0) for
62+// the imported timeline, refuses an unknown id and allows importing it again.
63+func TestDestroyTimeline(t *testing.T) {
64+ c, srv := connectWire(t, surfaceGlobals, surfaceOutputs)
65+ s, err := c.NewToplevel("tl", 64, 48)
66+ require.NoError(t, err)
67+ memfd, err := unix.MemfdCreate("tl", unix.MFD_CLOEXEC)
68+ require.NoError(t, err)
69+ defer unix.Close(memfd)
70+ require.NoError(t, s.ImportTimeline(7, memfd))
71+ require.NoError(t, c.Roundtrip())
72+
73+ var destroys atomic.Int32
74+ srv.mu.Lock()
75+ srv.hook = func(_ uint32, op uint16, _ []byte) {
76+ if op == 0 {
77+ destroys.Add(1)
78+ }
79+ }
80+ srv.mu.Unlock()
81+ require.NoError(t, s.DestroyTimeline(7))
82+ require.NoError(t, c.Roundtrip())
83+ require.Equal(t, int32(1), destroys.Load(), "one destroy request")
84+ require.ErrorContains(t, s.DestroyTimeline(7), "unknown timeline 7")
85+ require.ErrorContains(t, s.DestroyTimeline(99), "unknown timeline 99")
86+ require.NoError(t, s.ImportTimeline(7, memfd), "the id is free again")
87+}
88+
61 // requestLog counts the commits the wire peer sees.89 // requestLog counts the commits the wire peer sees.
62 type requestLog struct{ commits atomic.Int32 }90 type requestLog struct{ commits atomic.Int32 }
63 91

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 4

↑↓ moveenter openesc close