diff --git a/core/internal/screenshot/region.go b/core/internal/screenshot/region.go index 176e9d9d..1a8eb96f 100644 --- a/core/internal/screenshot/region.go +++ b/core/internal/screenshot/region.go @@ -14,8 +14,9 @@ import ( ) type SelectionState struct { - hasSelection bool // There's a selection to display (pre-loaded or user-drawn) - dragging bool // User is actively drawing a new selection + hasSelection bool // There's a selection to display (pre-loaded or user-drawn) + dragging bool // User is actively drawing a new selection + surface *OutputSurface // Surface where selection was made // Surface-local logical coordinates (from pointer events) anchorX float64 anchorY float64 @@ -88,6 +89,9 @@ type RegionSelector struct { running bool cancelled bool result Region + + capturedBuffer *ShmBuffer + capturedRegion Region } func NewRegionSelector(s *Screenshoter) *RegionSelector { @@ -98,59 +102,72 @@ func NewRegionSelector(s *Screenshoter) *RegionSelector { } } -func (r *RegionSelector) Run() (Region, bool, error) { +func (r *RegionSelector) Run() (*CaptureResult, bool, error) { r.preSelect = GetLastRegion() if err := r.connect(); err != nil { - return Region{}, false, fmt.Errorf("wayland connect: %w", err) + return nil, false, fmt.Errorf("wayland connect: %w", err) } defer r.cleanup() if err := r.setupRegistry(); err != nil { - return Region{}, false, fmt.Errorf("registry setup: %w", err) + return nil, false, fmt.Errorf("registry setup: %w", err) } if err := r.roundtrip(); err != nil { - return Region{}, false, fmt.Errorf("roundtrip after registry: %w", err) + return nil, false, fmt.Errorf("roundtrip after registry: %w", err) } switch { case r.screencopy == nil: - return Region{}, false, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1") + return nil, false, fmt.Errorf("compositor does not support wlr-screencopy-unstable-v1") case r.layerShell == nil: - return Region{}, false, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1") + return nil, false, fmt.Errorf("compositor does not support wlr-layer-shell-unstable-v1") case r.seat == nil: - return Region{}, false, fmt.Errorf("no seat available") + return nil, false, fmt.Errorf("no seat available") case r.compositor == nil: - return Region{}, false, fmt.Errorf("compositor not available") + return nil, false, fmt.Errorf("compositor not available") case r.shm == nil: - return Region{}, false, fmt.Errorf("wl_shm not available") + return nil, false, fmt.Errorf("wl_shm not available") case len(r.outputs) == 0: - return Region{}, false, fmt.Errorf("no outputs available") + return nil, false, fmt.Errorf("no outputs available") } if err := r.roundtrip(); err != nil { - return Region{}, false, fmt.Errorf("roundtrip after protocol check: %w", err) + return nil, false, fmt.Errorf("roundtrip after protocol check: %w", err) } if err := r.createSurfaces(); err != nil { - return Region{}, false, fmt.Errorf("create surfaces: %w", err) + return nil, false, fmt.Errorf("create surfaces: %w", err) } _ = r.createCursor() if err := r.roundtrip(); err != nil { - return Region{}, false, fmt.Errorf("roundtrip after surfaces: %w", err) + return nil, false, fmt.Errorf("roundtrip after surfaces: %w", err) } r.running = true for r.running { if err := r.ctx.Dispatch(); err != nil { - return Region{}, false, fmt.Errorf("dispatch: %w", err) + return nil, false, fmt.Errorf("dispatch: %w", err) } } - return r.result, r.cancelled, nil + if r.cancelled || r.capturedBuffer == nil { + return nil, r.cancelled, nil + } + + yInverted := false + if r.selection.surface != nil { + yInverted = r.selection.surface.yInverted + } + + return &CaptureResult{ + Buffer: r.capturedBuffer, + Region: r.result, // Global coords for saving last region + YInverted: yInverted, + }, false, nil } func (r *RegionSelector) connect() error { @@ -610,6 +627,7 @@ func (r *RegionSelector) applyPreSelection(os *OutputSurface) { r.selection.hasSelection = true r.selection.dragging = false + r.selection.surface = os r.selection.anchorX = x1 r.selection.anchorY = y1 r.selection.currentX = x2 diff --git a/core/internal/screenshot/region_input.go b/core/internal/screenshot/region_input.go index ba18aa2f..bbf8809b 100644 --- a/core/internal/screenshot/region_input.go +++ b/core/internal/screenshot/region_input.go @@ -71,6 +71,7 @@ func (r *RegionSelector) setupPointerHandlers() { case 1: // pressed r.selection.hasSelection = true r.selection.dragging = true + r.selection.surface = r.activeSurface // Lock to this surface r.selection.anchorX = r.pointerX r.selection.anchorY = r.pointerY r.selection.currentX = r.pointerX @@ -115,12 +116,17 @@ func (r *RegionSelector) setupKeyboardHandlers() { } func (r *RegionSelector) finishSelection() { - if r.activeSurface == nil { + if r.selection.surface == nil { r.running = false return } - os := r.activeSurface + os := r.selection.surface + srcBuf := r.getSourceBuffer(os) + if srcBuf == nil { + r.running = false + return + } x1, y1 := r.selection.anchorX, r.selection.anchorY x2, y2 := r.selection.currentX, r.selection.currentY @@ -133,13 +139,29 @@ func (r *RegionSelector) finishSelection() { } scaleX, scaleY := 1.0, 1.0 - if os.logicalW > 0 && os.screenBuf != nil { - scaleX = float64(os.screenBuf.Width) / float64(os.logicalW) - scaleY = float64(os.screenBuf.Height) / float64(os.logicalH) + if os.logicalW > 0 { + scaleX = float64(srcBuf.Width) / float64(os.logicalW) + scaleY = float64(srcBuf.Height) / float64(os.logicalH) } - bx1, by1 := int32(x1*scaleX), int32(y1*scaleY) - bx2, by2 := int32(x2*scaleX), int32(y2*scaleY) + bx1 := int(x1 * scaleX) + by1 := int(y1 * scaleY) + bx2 := int(x2 * scaleX) + by2 := int(y2 * scaleY) + + // Clamp to buffer bounds + if bx1 < 0 { + bx1 = 0 + } + if by1 < 0 { + by1 = 0 + } + if bx2 > srcBuf.Width { + bx2 = srcBuf.Width + } + if by2 > srcBuf.Height { + by2 = srcBuf.Height + } w, h := bx2-bx1, by2-by1 if w < 1 { @@ -149,11 +171,51 @@ func (r *RegionSelector) finishSelection() { h = 1 } + // Create cropped buffer and copy pixels directly + cropped, err := CreateShmBuffer(w, h, w*4) + if err != nil { + r.running = false + return + } + + srcData := srcBuf.Data() + dstData := cropped.Data() + for y := 0; y < h; y++ { + srcY := by1 + y + if srcY >= srcBuf.Height { + break + } + for x := 0; x < w; x++ { + srcX := bx1 + x + if srcX >= srcBuf.Width { + break + } + si := srcY*srcBuf.Stride + srcX*4 + di := y*cropped.Stride + x*4 + if si+3 < len(srcData) && di+3 < len(dstData) { + dstData[di+0] = srcData[si+0] + dstData[di+1] = srcData[si+1] + dstData[di+2] = srcData[si+2] + dstData[di+3] = srcData[si+3] + } + } + } + + r.capturedBuffer = cropped + r.capturedRegion = Region{ + X: int32(bx1), + Y: int32(by1), + Width: int32(w), + Height: int32(h), + Output: os.output.name, + } + + // Also store for "last region" feature with global coords r.result = Region{ - X: bx1 + os.output.x, - Y: by1 + os.output.y, - Width: w, - Height: h, + X: int32(bx1) + os.output.x, + Y: int32(by1) + os.output.y, + Width: int32(w), + Height: int32(h), Output: os.output.name, } diff --git a/core/internal/screenshot/region_render.go b/core/internal/screenshot/region_render.go index 0056619d..84c3bd7c 100644 --- a/core/internal/screenshot/region_render.go +++ b/core/internal/screenshot/region_render.go @@ -72,7 +72,7 @@ func (r *RegionSelector) drawOverlay(os *OutputSurface, renderBuf *ShmBuffer) { r.drawHUD(data, stride, w, h) - if !r.selection.hasSelection || r.activeSurface != os { + if !r.selection.hasSelection || r.selection.surface != os { return } diff --git a/core/internal/screenshot/screenshot.go b/core/internal/screenshot/screenshot.go index 97ecb51b..459d63a8 100644 --- a/core/internal/screenshot/screenshot.go +++ b/core/internal/screenshot/screenshot.go @@ -103,24 +103,19 @@ func (s *Screenshoter) captureLastRegion() (*CaptureResult, error) { func (s *Screenshoter) captureRegion() (*CaptureResult, error) { selector := NewRegionSelector(s) - region, cancelled, err := selector.Run() + result, cancelled, err := selector.Run() if err != nil { return nil, fmt.Errorf("region selection: %w", err) } - if cancelled { + if cancelled || result == nil { return nil, nil } - output := s.findOutputForRegion(region) - if output == nil { - return nil, fmt.Errorf("no output found for region") - } - - if err := SaveLastRegion(region); err != nil { + if err := SaveLastRegion(result.Region); err != nil { log.Debug("failed to save last region", "err", err) } - return s.captureRegionOnOutput(output, region) + return result, nil } func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) {