1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-03 20:32:07 -04:00

screenshot: fix scaling of global coordinate space when using all

screens
This commit is contained in:
bbedward
2026-03-31 15:13:10 -04:00
parent 3d9bd73336
commit 28f9aabcd9
2 changed files with 125 additions and 83 deletions

View File

@@ -444,20 +444,21 @@ func GetFocusedMonitor() string {
type outputInfo struct { type outputInfo struct {
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
func getOutputInfo(outputName string) (*outputInfo, bool) { func getAllOutputInfos() map[string]*outputInfo {
display, err := client.Connect("") display, err := client.Connect("")
if err != nil { if err != nil {
return nil, false return nil
} }
ctx := display.Context() ctx := display.Context()
defer ctx.Close() defer ctx.Close()
registry, err := display.GetRegistry() registry, err := display.GetRegistry()
if err != nil { if err != nil {
return nil, false return nil
} }
var outputManager *wlr_output_management.ZwlrOutputManagerV1 var outputManager *wlr_output_management.ZwlrOutputManagerV1
@@ -476,16 +477,17 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
}) })
if err := wlhelpers.Roundtrip(display, ctx); err != nil { if err := wlhelpers.Roundtrip(display, ctx); err != nil {
return nil, false return nil
} }
if outputManager == nil { if outputManager == nil {
return nil, false return nil
} }
type headState struct { type headState struct {
name string name string
x, y int32 x, y int32
scale float64
transform int32 transform int32
} }
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState) heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
@@ -501,6 +503,9 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
state.x = pe.X state.x = pe.X
state.y = pe.Y state.y = pe.Y
}) })
e.Head.SetScaleHandler(func(se wlr_output_management.ZwlrOutputHeadV1ScaleEvent) {
state.scale = se.Scale
})
e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) { e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
state.transform = te.Transform state.transform = te.Transform
}) })
@@ -511,21 +516,32 @@ func getOutputInfo(outputName string) (*outputInfo, bool) {
for !done { for !done {
if err := ctx.Dispatch(); err != nil { if err := ctx.Dispatch(); err != nil {
return nil, false return nil
} }
} }
result := make(map[string]*outputInfo, len(heads))
for _, state := range heads { for _, state := range heads {
if state.name == outputName { if state.name == "" {
return &outputInfo{ continue
x: state.x, }
y: state.y, result[state.name] = &outputInfo{
transform: state.transform, x: state.x,
}, true y: state.y,
scale: state.scale,
transform: state.transform,
} }
} }
return result
}
return nil, false func getOutputInfo(outputName string) (*outputInfo, bool) {
infos := getAllOutputInfos()
if infos == nil {
return nil, false
}
info, ok := infos[outputName]
return info, ok
} }
func getDWLActiveWindow() (*WindowGeometry, error) { func getDWLActiveWindow() (*WindowGeometry, error) {

View File

@@ -2,6 +2,7 @@ package screenshot
import ( import (
"fmt" "fmt"
"math"
"sync" "sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/log"
@@ -304,22 +305,20 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
if len(outputs) == 0 { if len(outputs) == 0 {
return nil, fmt.Errorf("no outputs available") return nil, fmt.Errorf("no outputs available")
} }
if len(outputs) == 1 { if len(outputs) == 1 {
return s.captureWholeOutput(outputs[0]) return s.captureWholeOutput(outputs[0])
} }
// Capture all outputs first to get actual buffer sizes wlrInfos := getAllOutputInfos()
type capturedOutput struct {
output *WaylandOutput
result *CaptureResult
physX int
physY int
}
captured := make([]capturedOutput, 0, len(outputs))
var minX, minY, maxX, maxY int type pendingOutput struct {
first := true result *CaptureResult
logX float64
logY float64
scale float64
}
var pending []pendingOutput
maxScale := 1.0
for _, output := range outputs { for _, output := range outputs {
result, err := s.captureWholeOutput(output) result, err := s.captureWholeOutput(output)
@@ -328,50 +327,74 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
continue continue
} }
outX, outY := output.x, output.y logX, logY := float64(output.x), float64(output.y)
scale := float64(output.scale) scale := float64(output.scale)
switch DetectCompositor() { switch DetectCompositor() {
case CompositorHyprland: case CompositorHyprland:
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok { if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
outX, outY = hx, hy logX, logY = float64(hx), float64(hy)
} }
if s := GetHyprlandMonitorScale(output.name); s > 0 { if hs := GetHyprlandMonitorScale(output.name); hs > 0 {
scale = s scale = hs
} }
case CompositorDWL: default:
if info, ok := getOutputInfo(output.name); ok { if wlrInfos != nil {
outX, outY = info.x, info.y if info, ok := wlrInfos[output.name]; ok {
logX, logY = float64(info.x), float64(info.y)
if info.scale > 0 {
scale = info.scale
}
}
} }
} }
if scale <= 0 { if scale <= 0 {
scale = 1.0 scale = 1.0
} }
physX := int(float64(outX) * scale) pending = append(pending, pendingOutput{result: result, logX: logX, logY: logY, scale: scale})
physY := int(float64(outY) * scale) if scale > maxScale {
maxScale = scale
}
}
captured = append(captured, capturedOutput{ if len(pending) == 0 {
output: output, return nil, fmt.Errorf("failed to capture any outputs")
result: result, }
physX: physX, if len(pending) == 1 {
physY: physY, return pending[0].result, nil
}) }
right := physX + result.Buffer.Width type layoutEntry struct {
bottom := physY + result.Buffer.Height result *CaptureResult
canvasX int
canvasY int
canvasW int
canvasH int
}
entries := make([]layoutEntry, len(pending))
var minX, minY, maxX, maxY int
if first { for i, p := range pending {
minX, minY = physX, physY cx := int(math.Round(p.logX * maxScale))
maxX, maxY = right, bottom cy := int(math.Round(p.logY * maxScale))
first = false cw := int(math.Round(float64(p.result.Buffer.Width) * maxScale / p.scale))
ch := int(math.Round(float64(p.result.Buffer.Height) * maxScale / p.scale))
entries[i] = layoutEntry{result: p.result, canvasX: cx, canvasY: cy, canvasW: cw, canvasH: ch}
right := cx + cw
bottom := cy + ch
if i == 0 {
minX, minY, maxX, maxY = cx, cy, right, bottom
continue continue
} }
if cx < minX {
if physX < minX { minX = cx
minX = physX
} }
if physY < minY { if cy < minY {
minY = physY minY = cy
} }
if right > maxX { if right > maxX {
maxX = right maxX = right
@@ -381,35 +404,26 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
} }
} }
if len(captured) == 0 {
return nil, fmt.Errorf("failed to capture any outputs")
}
if len(captured) == 1 {
return captured[0].result, nil
}
totalW := maxX - minX totalW := maxX - minX
totalH := maxY - minY totalH := maxY - minY
composite, err := CreateShmBuffer(totalW, totalH, totalW*4)
compositeStride := totalW * 4
composite, err := CreateShmBuffer(totalW, totalH, compositeStride)
if err != nil { if err != nil {
for _, c := range captured { for _, e := range entries {
c.result.Buffer.Close() e.result.Buffer.Close()
} }
return nil, fmt.Errorf("create composite buffer: %w", err) return nil, fmt.Errorf("create composite buffer: %w", err)
} }
composite.Clear() composite.Clear()
var format uint32 var format uint32
for _, c := range captured { for _, e := range entries {
if format == 0 { if format == 0 {
format = c.result.Format format = e.result.Format
} }
s.blitBuffer(composite, c.result.Buffer, c.physX-minX, c.physY-minY, c.result.YInverted) s.blitBufferScaled(composite, e.result.Buffer,
c.result.Buffer.Close() e.canvasX-minX, e.canvasY-minY, e.canvasW, e.canvasH,
e.result.YInverted)
e.result.Buffer.Close()
} }
return &CaptureResult{ return &CaptureResult{
@@ -419,32 +433,44 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
}, nil }, nil
} }
func (s *Screenshoter) blitBuffer(dst, src *ShmBuffer, dstX, dstY int, yInverted bool) { func (s *Screenshoter) blitBufferScaled(dst, src *ShmBuffer, dstX, dstY, dstW, dstH int, yInverted bool) {
if dstW <= 0 || dstH <= 0 {
return
}
srcData := src.Data() srcData := src.Data()
dstData := dst.Data() dstData := dst.Data()
for srcY := 0; srcY < src.Height; srcY++ { for dy := 0; dy < dstH; dy++ {
actualSrcY := srcY canvasY := dstY + dy
if yInverted { if canvasY < 0 || canvasY >= dst.Height {
actualSrcY = src.Height - 1 - srcY
}
dy := dstY + srcY
if dy < 0 || dy >= dst.Height {
continue continue
} }
srcRowOff := actualSrcY * src.Stride srcY := dy * src.Height / dstH
dstRowOff := dy * dst.Stride if yInverted {
srcY = src.Height - 1 - srcY
}
if srcY < 0 || srcY >= src.Height {
continue
}
for srcX := 0; srcX < src.Width; srcX++ { srcRowOff := srcY * src.Stride
dx := dstX + srcX dstRowOff := canvasY * dst.Stride
if dx < 0 || dx >= dst.Width {
for dx := 0; dx < dstW; dx++ {
canvasX := dstX + dx
if canvasX < 0 || canvasX >= dst.Width {
continue
}
srcX := dx * src.Width / dstW
if srcX >= src.Width {
continue continue
} }
si := srcRowOff + srcX*4 si := srcRowOff + srcX*4
di := dstRowOff + dx*4 di := dstRowOff + canvasX*4
if si+3 >= len(srcData) || di+3 >= len(dstData) { if si+3 >= len(srcData) || di+3 >= len(dstData) {
continue continue