detached 1988d1e

fix(examples): gate first present, shutdown order, SIGTERM, timeline cleanup in layer

commit
1988d1edfc30509bec290f11afdaa2c7f9576c8c
author
brice · · 2026-10-01 19:23 UTC
parent
7c37286
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +26 -5
M examples/layer/main.go +26 -5
@@ -19,6 +19,7 @@ import (
1919 "fmt"
2020 "os"
2121 "os/signal"
22+ "syscall"
2223 "time"
2324
2425 "github.com/bnema/neferclient"
@@ -30,6 +31,10 @@ import (
3031 const (
3132 surfaceW, surfaceH = 240, 90
3233 buttonColor = "#2060c0"
34+
35+ // guiMods is the set of modifiers NeferGUI knows. neferclient and NeferGUI
36+ // use the same bit order for them, so the plain bits can be copied.
37+ guiMods = neferclient.ModShift | neferclient.ModCtrl | neferclient.ModAlt | neferclient.ModSuper
3338 )
3439
3540 type model struct{ count int }
@@ -71,7 +76,7 @@ type app struct {
7176 func main() {
7277 frames := flag.Int("frames", 0, "redraw continuously and exit after this many frames (0: run until closed)")
7378 flag.Parse()
74- ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
79+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
7580 defer stop()
7681 if err := run(ctx, *frames); err != nil {
7782 fmt.Fprintln(os.Stderr, "layer:", err)
@@ -85,11 +90,14 @@ func run(ctx context.Context, frames int) (err error) {
8590 return fmt.Errorf("connect: %w", err)
8691 }
8792 a := &app{conn: conn, frames: frames, cursor: neferclient.CursorDefault}
93+ // Shutdown order: close the connection first, which drops its descriptor
94+ // watches and destroys every Wayland object, then free the renderer and
95+ // its GPU resources (including the release eventfds).
8896 defer func() {
97+ err = errors.Join(err, conn.Close())
8998 if a.r != nil {
9099 err = errors.Join(err, a.r.Close())
91100 }
92- err = errors.Join(err, conn.Close())
93101 }()
94102 a.surf, err = conn.NewLayerSurface(neferclient.LayerConfig{
95103 Level: neferclient.LayerTop,
@@ -107,7 +115,7 @@ func run(ctx context.Context, frames int) (err error) {
107115 for !a.done {
108116 select {
109117 case <-ctx.Done():
110- return nil
118+ return nil // interrupt or SIGTERM: a clean exit
111119 case <-conn.Wake():
112120 if err = conn.Dispatch(a); err != nil {
113121 return fmt.Errorf("dispatch: %w", err)
@@ -131,10 +139,19 @@ func run(ctx context.Context, frames int) (err error) {
131139
132140 // FeedbackDone: dmabuf feedback → nefergui.RendererConfig. The renderer opens
133141 // the compositor's main device and picks a format and modifier from the list.
142+//
143+// Only the first complete feedback is used. A later one (the compositor
144+// changing its preferred device or formats) is ignored: this demo assumes a
145+// single GPU, and rebuilding the renderer is application policy that would
146+// obscure the glue. A real application would create a new Renderer and
147+// re-import its buffers here.
134148 func (a *app) FeedbackDone(neferclient.SurfaceID) { a.setup() }
135149
136150 func (a *app) Configure(neferclient.SurfaceID, int32, int32) {
137- a.configured, a.canPresent = true, true
151+ if !a.configured { // later configures must not lift the frame-callback gate
152+ a.canPresent = true
153+ }
154+ a.configured = true
138155 a.resize()
139156 a.setup()
140157 }
@@ -240,7 +257,7 @@ func (a *app) Key(ev *neferclient.KeyEvent) {
240257 Text: ev.Text, // valid only during the call, as for Input
241258 Pressed: ev.Pressed,
242259 Repeat: ev.Repeat,
243- Modifiers: nefergui.Modifiers(ev.Modifiers & 0x0f), // shift, ctrl, alt, super
260+ Modifiers: nefergui.Modifiers(ev.Modifiers & guiMods),
244261 })
245262 }
246263
@@ -284,6 +301,10 @@ func (a *app) present() error {
284301 if err := a.surf.DestroyBuffer(rt.Buffer); err != nil {
285302 return fmt.Errorf("destroy retired buffer %d: %w", rt.Buffer, err)
286303 }
304+ // The release timeline of a buffer is imported under the buffer's id.
305+ if err := a.surf.DestroyTimeline(rt.Buffer); err != nil {
306+ return fmt.Errorf("destroy retired timeline %d: %w", rt.Buffer, err)
307+ }
287308 }
288309 if out.NewBuffer {
289310 buf := neferclient.Buffer{
@@ -19,6 +19,7 @@ import (
19 "fmt"19 "fmt"
20 "os"20 "os"
21 "os/signal"21 "os/signal"
22+ "syscall"
22 "time"23 "time"
23 24
24 "github.com/bnema/neferclient"25 "github.com/bnema/neferclient"
@@ -30,6 +31,10 @@ import (
30 const (31 const (
31 surfaceW, surfaceH = 240, 9032 surfaceW, surfaceH = 240, 90
32 buttonColor = "#2060c0"33 buttonColor = "#2060c0"
34+
35+ // guiMods is the set of modifiers NeferGUI knows. neferclient and NeferGUI
36+ // use the same bit order for them, so the plain bits can be copied.
37+ guiMods = neferclient.ModShift | neferclient.ModCtrl | neferclient.ModAlt | neferclient.ModSuper
33 )38 )
34 39
35 type model struct{ count int }40 type model struct{ count int }
@@ -71,7 +76,7 @@ type app struct {
71 func main() {76 func main() {
72 frames := flag.Int("frames", 0, "redraw continuously and exit after this many frames (0: run until closed)")77 frames := flag.Int("frames", 0, "redraw continuously and exit after this many frames (0: run until closed)")
73 flag.Parse()78 flag.Parse()
74- ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)79+ ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
75 defer stop()80 defer stop()
76 if err := run(ctx, *frames); err != nil {81 if err := run(ctx, *frames); err != nil {
77 fmt.Fprintln(os.Stderr, "layer:", err)82 fmt.Fprintln(os.Stderr, "layer:", err)
@@ -85,11 +90,14 @@ func run(ctx context.Context, frames int) (err error) {
85 return fmt.Errorf("connect: %w", err)90 return fmt.Errorf("connect: %w", err)
86 }91 }
87 a := &app{conn: conn, frames: frames, cursor: neferclient.CursorDefault}92 a := &app{conn: conn, frames: frames, cursor: neferclient.CursorDefault}
93+ // Shutdown order: close the connection first, which drops its descriptor
94+ // watches and destroys every Wayland object, then free the renderer and
95+ // its GPU resources (including the release eventfds).
88 defer func() {96 defer func() {
97+ err = errors.Join(err, conn.Close())
89 if a.r != nil {98 if a.r != nil {
90 err = errors.Join(err, a.r.Close())99 err = errors.Join(err, a.r.Close())
91 }100 }
92- err = errors.Join(err, conn.Close())
93 }()101 }()
94 a.surf, err = conn.NewLayerSurface(neferclient.LayerConfig{102 a.surf, err = conn.NewLayerSurface(neferclient.LayerConfig{
95 Level: neferclient.LayerTop,103 Level: neferclient.LayerTop,
@@ -107,7 +115,7 @@ func run(ctx context.Context, frames int) (err error) {
107 for !a.done {115 for !a.done {
108 select {116 select {
109 case <-ctx.Done():117 case <-ctx.Done():
110- return nil118+ return nil // interrupt or SIGTERM: a clean exit
111 case <-conn.Wake():119 case <-conn.Wake():
112 if err = conn.Dispatch(a); err != nil {120 if err = conn.Dispatch(a); err != nil {
113 return fmt.Errorf("dispatch: %w", err)121 return fmt.Errorf("dispatch: %w", err)
@@ -131,10 +139,19 @@ func run(ctx context.Context, frames int) (err error) {
131 139
132 // FeedbackDone: dmabuf feedback → nefergui.RendererConfig. The renderer opens140 // FeedbackDone: dmabuf feedback → nefergui.RendererConfig. The renderer opens
133 // the compositor's main device and picks a format and modifier from the list.141 // the compositor's main device and picks a format and modifier from the list.
142+//
143+// Only the first complete feedback is used. A later one (the compositor
144+// changing its preferred device or formats) is ignored: this demo assumes a
145+// single GPU, and rebuilding the renderer is application policy that would
146+// obscure the glue. A real application would create a new Renderer and
147+// re-import its buffers here.
134 func (a *app) FeedbackDone(neferclient.SurfaceID) { a.setup() }148 func (a *app) FeedbackDone(neferclient.SurfaceID) { a.setup() }
135 149
136 func (a *app) Configure(neferclient.SurfaceID, int32, int32) {150 func (a *app) Configure(neferclient.SurfaceID, int32, int32) {
137- a.configured, a.canPresent = true, true151+ if !a.configured { // later configures must not lift the frame-callback gate
152+ a.canPresent = true
153+ }
154+ a.configured = true
138 a.resize()155 a.resize()
139 a.setup()156 a.setup()
140 }157 }
@@ -240,7 +257,7 @@ func (a *app) Key(ev *neferclient.KeyEvent) {
240 Text: ev.Text, // valid only during the call, as for Input257 Text: ev.Text, // valid only during the call, as for Input
241 Pressed: ev.Pressed,258 Pressed: ev.Pressed,
242 Repeat: ev.Repeat,259 Repeat: ev.Repeat,
243- Modifiers: nefergui.Modifiers(ev.Modifiers & 0x0f), // shift, ctrl, alt, super260+ Modifiers: nefergui.Modifiers(ev.Modifiers & guiMods),
244 })261 })
245 }262 }
246 263
@@ -284,6 +301,10 @@ func (a *app) present() error {
284 if err := a.surf.DestroyBuffer(rt.Buffer); err != nil {301 if err := a.surf.DestroyBuffer(rt.Buffer); err != nil {
285 return fmt.Errorf("destroy retired buffer %d: %w", rt.Buffer, err)302 return fmt.Errorf("destroy retired buffer %d: %w", rt.Buffer, err)
286 }303 }
304+ // The release timeline of a buffer is imported under the buffer's id.
305+ if err := a.surf.DestroyTimeline(rt.Buffer); err != nil {
306+ return fmt.Errorf("destroy retired timeline %d: %w", rt.Buffer, err)
307+ }
287 }308 }
288 if out.NewBuffer {309 if out.NewBuffer {
289 buf := neferclient.Buffer{310 buf := neferclient.Buffer{

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