From 2ddc448150b0576afe528ae5700ac031f94c9547 Mon Sep 17 00:00:00 2001 From: bbedward Date: Fri, 5 Dec 2025 17:39:35 -0500 Subject: [PATCH] screenshot: ensure screencopy before surface creation --- core/internal/screenshot/region.go | 189 ++++++++++++++-------- core/internal/screenshot/region_render.go | 63 +++++--- 2 files changed, 157 insertions(+), 95 deletions(-) diff --git a/core/internal/screenshot/region.go b/core/internal/screenshot/region.go index a506e07f..4218f034 100644 --- a/core/internal/screenshot/region.go +++ b/core/internal/screenshot/region.go @@ -49,6 +49,13 @@ type OutputSurface struct { slotsReady bool } +type PreCapture struct { + screenBuf *ShmBuffer + screenBufNoCursor *ShmBuffer + format uint32 + yInverted bool +} + type RegionSelector struct { screenshoter *Screenshoter @@ -68,8 +75,9 @@ type RegionSelector struct { shortcutsInhibitMgr *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitManagerV1 shortcutsInhibitor *keyboard_shortcuts_inhibit.ZwpKeyboardShortcutsInhibitorV1 - outputs map[uint32]*WaylandOutput - outputsMu sync.Mutex + outputs map[uint32]*WaylandOutput + outputsMu sync.Mutex + preCapture map[*WaylandOutput]*PreCapture surfaces []*OutputSurface activeSurface *OutputSurface @@ -99,6 +107,7 @@ func NewRegionSelector(s *Screenshoter) *RegionSelector { return &RegionSelector{ screenshoter: s, outputs: make(map[uint32]*WaylandOutput), + preCapture: make(map[*WaylandOutput]*PreCapture), showCapturedCursor: true, } } @@ -138,6 +147,10 @@ func (r *RegionSelector) Run() (*CaptureResult, bool, error) { return nil, false, fmt.Errorf("roundtrip after protocol check: %w", err) } + if err := r.preCaptureAllOutputs(); err != nil { + return nil, false, fmt.Errorf("pre-capture: %w", err) + } + if err := r.createSurfaces(); err != nil { return nil, false, fmt.Errorf("create surfaces: %w", err) } @@ -320,6 +333,102 @@ func (r *RegionSelector) setupOutputHandlers(name uint32, output *client.Output) }) } +func (r *RegionSelector) preCaptureAllOutputs() error { + r.outputsMu.Lock() + outputs := make([]*WaylandOutput, 0, len(r.outputs)) + for _, o := range r.outputs { + outputs = append(outputs, o) + } + r.outputsMu.Unlock() + + pending := len(outputs) * 2 + done := make(chan struct{}, pending) + + for _, output := range outputs { + pc := &PreCapture{} + r.preCapture[output] = pc + + r.preCaptureOutput(output, pc, true, func() { done <- struct{}{} }) + r.preCaptureOutput(output, pc, false, func() { done <- struct{}{} }) + } + + for i := 0; i < pending; i++ { + if err := r.ctx.Dispatch(); err != nil { + return err + } + select { + case <-done: + default: + i-- + } + } + return nil +} + +func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, withCursor bool, onReady func()) { + cursor := int32(0) + if withCursor { + cursor = 1 + } + + frame, err := r.screencopy.CaptureOutput(cursor, output.wlOutput) + if err != nil { + log.Error("screencopy capture failed", "err", err) + onReady() + return + } + + frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { + buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride)) + if err != nil { + log.Error("create screen buffer failed", "err", err) + return + } + + if withCursor { + pc.screenBuf = buf + pc.format = e.Format + } else { + pc.screenBufNoCursor = buf + } + + pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) + if err != nil { + log.Error("create shm pool failed", "err", err) + return + } + + wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), e.Format) + if err != nil { + log.Error("create wl_buffer failed", "err", err) + pool.Destroy() + return + } + + if err := frame.Copy(wlBuf); err != nil { + log.Error("frame copy failed", "err", err) + } + pool.Destroy() + }) + + frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) { + if withCursor { + pc.yInverted = (e.Flags & 1) != 0 + } + }) + + frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) { + frame.Destroy() + onReady() + }) + + frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) { + log.Error("screencopy failed") + frame.Destroy() + onReady() + }) +} + func (r *RegionSelector) createSurfaces() error { r.outputsMu.Lock() outputs := make([]*WaylandOutput, 0, len(r.outputs)) @@ -488,77 +597,19 @@ func (r *RegionSelector) ensureShortcutsInhibitor(os *OutputSurface) { } func (r *RegionSelector) captureForSurface(os *OutputSurface) { - r.captureWithCursor(os, true, func() { - r.captureWithCursor(os, false, func() { - r.initRenderBuffer(os) - r.applyPreSelection(os) - r.redrawSurface(os) - }) - }) -} - -func (r *RegionSelector) captureWithCursor(os *OutputSurface, withCursor bool, onReady func()) { - cursor := int32(0) - if withCursor { - cursor = 1 - } - - frame, err := r.screencopy.CaptureOutput(cursor, os.output.wlOutput) - if err != nil { - log.Error("screencopy capture failed", "err", err) + pc := r.preCapture[os.output] + if pc == nil { return } - frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) { - buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride)) - if err != nil { - log.Error("create screen buffer failed", "err", err) - return - } + os.screenBuf = pc.screenBuf + os.screenBufNoCursor = pc.screenBufNoCursor + os.screenFormat = pc.format + os.yInverted = pc.yInverted - if withCursor { - os.screenBuf = buf - os.screenFormat = e.Format - } else { - os.screenBufNoCursor = buf - } - - pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size())) - if err != nil { - log.Error("create shm pool failed", "err", err) - return - } - - wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), e.Format) - if err != nil { - log.Error("create wl_buffer failed", "err", err) - pool.Destroy() - return - } - - if err := frame.Copy(wlBuf); err != nil { - log.Error("frame copy failed", "err", err) - } - pool.Destroy() - }) - - frame.SetFlagsHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FlagsEvent) { - if withCursor { - os.yInverted = (e.Flags & 1) != 0 - } - }) - - frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) { - frame.Destroy() - if onReady != nil { - onReady() - } - }) - - frame.SetFailedHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1FailedEvent) { - log.Error("screencopy failed") - frame.Destroy() - }) + r.initRenderBuffer(os) + r.applyPreSelection(os) + r.redrawSurface(os) } func (r *RegionSelector) initRenderBuffer(os *OutputSurface) { diff --git a/core/internal/screenshot/region_render.go b/core/internal/screenshot/region_render.go index 8fe1e540..82bb1cca 100644 --- a/core/internal/screenshot/region_render.go +++ b/core/internal/screenshot/region_render.go @@ -55,6 +55,7 @@ func (r *RegionSelector) drawOverlay(os *OutputSurface, renderBuf *ShmBuffer) { data := renderBuf.Data() stride := renderBuf.Stride w, h := renderBuf.Width, renderBuf.Height + format := os.screenFormat // Dim the entire buffer for y := 0; y < h; y++ { @@ -70,7 +71,7 @@ func (r *RegionSelector) drawOverlay(os *OutputSurface, renderBuf *ShmBuffer) { } } - r.drawHUD(data, stride, w, h) + r.drawHUD(data, stride, w, h, format) if !r.selection.hasSelection || r.selection.surface != os { return @@ -121,11 +122,11 @@ func (r *RegionSelector) drawOverlay(os *OutputSurface, renderBuf *ShmBuffer) { selW = selH } } - r.drawBorder(data, stride, w, h, bx1, by1, selW, selH) - r.drawDimensions(data, stride, w, h, bx1, by1, selW, selH) + r.drawBorder(data, stride, w, h, bx1, by1, selW, selH, format) + r.drawDimensions(data, stride, w, h, bx1, by1, selW, selH, format) } -func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int) { +func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int, format uint32) { if r.selection.dragging { return } @@ -158,16 +159,16 @@ func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int) { hudY := bufH - hudH - 20 r.fillRect(data, stride, bufW, bufH, hudX, hudY, hudW, hudH, - style.BackgroundR, style.BackgroundG, style.BackgroundB, style.BackgroundA) + style.BackgroundR, style.BackgroundG, style.BackgroundB, style.BackgroundA, format) tx, ty := hudX+padding, hudY+padding for i, item := range items { r.drawText(data, stride, bufW, bufH, tx, ty, item.key, - style.AccentR, style.AccentG, style.AccentB) + style.AccentR, style.AccentG, style.AccentB, format) tx += len(item.key) * (charW + 1) r.drawText(data, stride, bufW, bufH, tx, ty, " "+item.desc, - style.TextR, style.TextG, style.TextB) + style.TextR, style.TextG, style.TextB, format) tx += (1 + len(item.desc)) * (charW + 1) if i < len(items)-1 { @@ -176,17 +177,17 @@ func (r *RegionSelector) drawHUD(data []byte, stride, bufW, bufH int) { } } -func (r *RegionSelector) drawBorder(data []byte, stride, bufW, bufH, x, y, w, h int) { +func (r *RegionSelector) drawBorder(data []byte, stride, bufW, bufH, x, y, w, h int, format uint32) { const thickness = 2 for i := 0; i < thickness; i++ { - r.drawHLine(data, stride, bufW, bufH, x-i, y-i, w+2*i) - r.drawHLine(data, stride, bufW, bufH, x-i, y+h+i-1, w+2*i) - r.drawVLine(data, stride, bufW, bufH, x-i, y-i, h+2*i) - r.drawVLine(data, stride, bufW, bufH, x+w+i-1, y-i, h+2*i) + r.drawHLine(data, stride, bufW, bufH, x-i, y-i, w+2*i, format) + r.drawHLine(data, stride, bufW, bufH, x-i, y+h+i-1, w+2*i, format) + r.drawVLine(data, stride, bufW, bufH, x-i, y-i, h+2*i, format) + r.drawVLine(data, stride, bufW, bufH, x+w+i-1, y-i, h+2*i, format) } } -func (r *RegionSelector) drawHLine(data []byte, stride, bufW, bufH, x, y, length int) { +func (r *RegionSelector) drawHLine(data []byte, stride, bufW, bufH, x, y, length int, _ uint32) { if y < 0 || y >= bufH { return } @@ -204,7 +205,7 @@ func (r *RegionSelector) drawHLine(data []byte, stride, bufW, bufH, x, y, length } } -func (r *RegionSelector) drawVLine(data []byte, stride, bufW, bufH, x, y, length int) { +func (r *RegionSelector) drawVLine(data []byte, stride, bufW, bufH, x, y, length int, _ uint32) { if x < 0 || x >= bufW { return } @@ -221,7 +222,7 @@ func (r *RegionSelector) drawVLine(data []byte, stride, bufW, bufH, x, y, length } } -func (r *RegionSelector) drawDimensions(data []byte, stride, bufW, bufH, x, y, w, h int) { +func (r *RegionSelector) drawDimensions(data []byte, stride, bufW, bufH, x, y, w, h int, format uint32) { text := fmt.Sprintf("%dx%d", w, h) const charW, charH = 8, 12 @@ -236,14 +237,19 @@ func (r *RegionSelector) drawDimensions(data []byte, stride, bufW, bufH, x, y, w } tx = clamp(tx, 0, bufW-textW) - r.fillRect(data, stride, bufW, bufH, tx-4, ty-2, textW+8, textH+4, 0, 0, 0, 200) - r.drawText(data, stride, bufW, bufH, tx, ty, text, 255, 255, 255) + r.fillRect(data, stride, bufW, bufH, tx-4, ty-2, textW+8, textH+4, 0, 0, 0, 200, format) + r.drawText(data, stride, bufW, bufH, tx, ty, text, 255, 255, 255, format) } -func (r *RegionSelector) fillRect(data []byte, stride, bufW, bufH, x, y, w, h int, br, bg, bb, ba uint8) { - alpha := float64(ba) / 255.0 +func (r *RegionSelector) fillRect(data []byte, stride, bufW, bufH, x, y, w, h int, cr, cg, cb, ca uint8, format uint32) { + alpha := float64(ca) / 255.0 invAlpha := 1.0 - alpha + c0, c2 := cb, cr + if format == uint32(FormatABGR8888) || format == uint32(FormatXBGR8888) { + c0, c2 = cr, cb + } + for py := y; py < y+h && py < bufH; py++ { if py < 0 { continue @@ -256,26 +262,31 @@ func (r *RegionSelector) fillRect(data []byte, stride, bufW, bufH, x, y, w, h in if off+3 >= len(data) { continue } - data[off+0] = uint8(float64(data[off+0])*invAlpha + float64(bb)*alpha) - data[off+1] = uint8(float64(data[off+1])*invAlpha + float64(bg)*alpha) - data[off+2] = uint8(float64(data[off+2])*invAlpha + float64(br)*alpha) + data[off+0] = uint8(float64(data[off+0])*invAlpha + float64(c0)*alpha) + data[off+1] = uint8(float64(data[off+1])*invAlpha + float64(cg)*alpha) + data[off+2] = uint8(float64(data[off+2])*invAlpha + float64(c2)*alpha) data[off+3] = 255 } } } -func (r *RegionSelector) drawText(data []byte, stride, bufW, bufH, x, y int, text string, cr, cg, cb uint8) { +func (r *RegionSelector) drawText(data []byte, stride, bufW, bufH, x, y int, text string, cr, cg, cb uint8, format uint32) { for i, ch := range text { - r.drawChar(data, stride, bufW, bufH, x+i*9, y, ch, cr, cg, cb) + r.drawChar(data, stride, bufW, bufH, x+i*9, y, ch, cr, cg, cb, format) } } -func (r *RegionSelector) drawChar(data []byte, stride, bufW, bufH, x, y int, ch rune, cr, cg, cb uint8) { +func (r *RegionSelector) drawChar(data []byte, stride, bufW, bufH, x, y int, ch rune, cr, cg, cb uint8, format uint32) { glyph, ok := fontGlyphs[ch] if !ok { return } + c0, c2 := cb, cr + if format == uint32(FormatABGR8888) || format == uint32(FormatXBGR8888) { + c0, c2 = cr, cb + } + for row := 0; row < 12; row++ { py := y + row if py < 0 || py >= bufH { @@ -294,7 +305,7 @@ func (r *RegionSelector) drawChar(data []byte, stride, bufW, bufH, x, y int, ch if off+3 >= len(data) { continue } - data[off], data[off+1], data[off+2], data[off+3] = cb, cg, cr, 255 + data[off], data[off+1], data[off+2], data[off+3] = c0, cg, c2, 255 } } }