diff --git a/fb.go b/fb.go index 0338558..d3f4a08 100644 --- a/fb.go +++ b/fb.go @@ -5,6 +5,7 @@ import ( "fmt" "math" "path/filepath" + "sync/atomic" "unsafe" "golang.org/x/sys/unix" @@ -49,6 +50,9 @@ type framebuffer struct { // One token per completed flip. The render loop waits on this rather than on // a timer, so drawing is paced by the panel instead of by a guess at its rate. flips chan struct{} + + // Set before the fd goes, so the event reader can tell shutdown from failure. + closing atomic.Bool } func (fb *framebuffer) offset(x, y int) int { @@ -275,18 +279,30 @@ func openFramebuffer() (*framebuffer, error) { } // Flip completions arrive on the drm fd as a stream of length-prefixed events. +// Nothing else feeds fb.flips, so returning early here freezes the panel on its +// last frame while everything else goes on running. func (fb *framebuffer) readEvents() { buf := make([]byte, 4096) for { n, err := unix.Read(fb.fd, buf) - if n <= 0 || err != nil { + if fb.closing.Load() { return } + if err == unix.EINTR || err == unix.EAGAIN { + continue + } + if err != nil { + panic(fmt.Sprintf("reading drm events: %v", err)) + } + if n == 0 { + panic("drm fd reported end of file") + } for off := 0; off+8 <= n; { typ := binary.LittleEndian.Uint32(buf[off:]) length := int(binary.LittleEndian.Uint32(buf[off+4:])) if length < 8 || off+length > n { - return + panic(fmt.Sprintf("drm event at offset %d claims %d bytes of %d read", + off, length, n)) } if typ == drm.EventFlipComplete { select { @@ -300,6 +316,7 @@ func (fb *framebuffer) readEvents() { } func (fb *framebuffer) close() { + fb.closing.Store(true) for i := range fb.bufs { if fb.bufs[i].mem != nil { unix.Munmap(fb.bufs[i].mem)