diff --git a/core/internal/screenshot/compositor.go b/core/internal/screenshot/compositor.go index 3464f62ca..0f0053573 100644 --- a/core/internal/screenshot/compositor.go +++ b/core/internal/screenshot/compositor.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/exec" + "sync" "github.com/duxlabs-dev/dux-go/wayland/client" "github.com/duxlabs-dev/dux-shell/core/internal/proto/wlr_output_management" @@ -151,6 +152,31 @@ func GetHyprlandMonitorScale(name string) float64 { return 0 } +// !TODO: remove with the frameInLogicalOrientation version fallback +var hyprlandVersion = sync.OnceValues(func() (int, int) { + output, err := exec.Command("hyprctl", "-j", "version").Output() + if err != nil { + return 0, 0 + } + var info struct { + Version string `json:"version"` + } + if err := json.Unmarshal(output, &info); err != nil { + return 0, 0 + } + var major, minor int + fmt.Sscanf(info.Version, "%d.%d", &major, &minor) + return major, minor +}) + +func hyprlandVersionAtLeast(major, minor int) bool { + haveMajor, haveMinor := hyprlandVersion() + if haveMajor != major { + return haveMajor > major + } + return haveMinor >= minor +} + func getHyprlandFocusedMonitor() string { output, err := exec.Command("hyprctl", "-j", "monitors").Output() if err != nil { diff --git a/core/internal/screenshot/region.go b/core/internal/screenshot/region.go index 3da2fd5bc..480015915 100644 --- a/core/internal/screenshot/region.go +++ b/core/internal/screenshot/region.go @@ -609,15 +609,12 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture, pc.yInverted = false } - if output.transform != TransformNormal { - invTransform := InverseTransform(output.transform) - transformed, err := capturedBuf.ApplyTransform(invTransform) - if err != nil { - log.Error("apply transform failed", "err", err) - } else if transformed != capturedBuf { - capturedBuf.Close() - capturedBuf = transformed - } + transformed, err := output.untransformFrame(capturedBuf) + if err != nil { + log.Error("apply transform failed", "err", err) + } else if transformed != capturedBuf { + capturedBuf.Close() + capturedBuf = transformed } if withCursor { diff --git a/core/internal/screenshot/screenshot.go b/core/internal/screenshot/screenshot.go index 1646a24a8..f35ac47cb 100644 --- a/core/internal/screenshot/screenshot.go +++ b/core/internal/screenshot/screenshot.go @@ -49,6 +49,28 @@ func (o *WaylandOutput) effectiveScale() float64 { return scale } +// Hyprland renders screencopy frames in the output's logical orientation since +// 901881c9f (v0.57.0); wlroots and niri still hand back the native framebuffer. +// !TODO: drop the version fallback once every distro ships hyprland >= 0.57 +func (o *WaylandOutput) frameInLogicalOrientation(frameW, frameH int) bool { + switch o.transform { + case TransformNormal: + return true + case Transform90, Transform270, TransformFlipped90, TransformFlipped270: + if o.width != o.height { + return frameW == int(o.height) && frameH == int(o.width) + } + } + return DetectCompositor() == CompositorHyprland && hyprlandVersionAtLeast(0, 57) +} + +func (o *WaylandOutput) untransformFrame(buf *ShmBuffer) (*ShmBuffer, error) { + if o.frameInLogicalOrientation(buf.Width, buf.Height) { + return buf, nil + } + return buf.ApplyTransform(InverseTransform(o.transform)) +} + func (o *WaylandOutput) bounds() Region { x, y, w, h := o.x, o.y, o.width, o.height if DetectCompositor() == CompositorHyprland { @@ -677,12 +699,7 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult result.YInverted = false } - if output.transform == TransformNormal { - return result, nil - } - - invTransform := InverseTransform(output.transform) - transformed, err := result.Buffer.ApplyTransform(invTransform) + transformed, err := output.untransformFrame(result.Buffer) if err != nil { result.Buffer.Close() return nil, fmt.Errorf("apply transform: %w", err)