package main import ( "bytes" "fmt" "image" "image/color" "path/filepath" "unsafe" "golang.org/x/sys/unix" "g.fc.run/theater/cabletest/internal/drm" ) // cabletest drives the panel through drm, so /dev/fb0 holds the kernel console // and is not worth reading. The displayed buffer is reachable from out here // instead: GETCRTC names it, and root is allowed a handle to it without being // drm master, so it can be read without the owning process cooperating. const drmCardGlob = "/dev/dri/card*" type grabber struct { fd int crtc uint32 index int } func openGrabber() (*grabber, error) { paths, err := filepath.Glob(drmCardGlob) if err != nil { return nil, err } for _, p := range paths { fd, err := unix.Open(p, unix.O_RDWR|unix.O_CLOEXEC, 0) if err != nil { continue } crtc, index, err := activeCrtc(fd) if err == nil { return &grabber{fd: fd, crtc: crtc, index: index}, nil } unix.Close(fd) } return nil, fmt.Errorf("no drm crtc is scanning out a framebuffer") } func activeCrtc(fd int) (uint32, int, error) { var res drm.ModeCardRes if err := drm.Ioctl(fd, drm.GetResources, unsafe.Pointer(&res)); err != nil { return 0, 0, err } if res.CountCRTCs == 0 { return 0, 0, fmt.Errorf("card has no crtcs") } crtcs := make([]uint32, res.CountCRTCs) res.CountFBs, res.CountConns, res.CountEncs = 0, 0, 0 res.FBIDPtr, res.ConnIDPtr, res.EncIDPtr = 0, 0, 0 res.CrtcIDPtr = uint64(uintptr(unsafe.Pointer(&crtcs[0]))) if err := drm.Ioctl(fd, drm.GetResources, unsafe.Pointer(&res)); err != nil { return 0, 0, err } for i, id := range crtcs { c := drm.ModeCrtc{CrtcID: id} if err := drm.Ioctl(fd, drm.GetCrtc, unsafe.Pointer(&c)); err != nil { continue } if c.FBID != 0 && c.ModeValid != 0 { return id, i, nil } } return 0, 0, fmt.Errorf("no crtc is scanning out a framebuffer") } func (g *grabber) close() { unix.Close(g.fd) } // Reading a buffer takes about as long as a frame, so where the read starts in // the flip cycle decides whether it stays ahead of the writer. Woken at a // blank, the buffer GETCRTC then names has just gone on screen. func (g *grabber) waitVblank() error { v := drm.WaitVblank{ Type: drm.VblankRelative | uint32(g.index<