mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
screenshot: handle transformed displays
This commit is contained in:
@@ -295,7 +295,14 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
|
|
||||||
data := buf.Data()
|
data := buf.Data()
|
||||||
rgb := make([]byte, dstW*dstH*3)
|
rgb := make([]byte, dstW*dstH*3)
|
||||||
swapRB := pixelFormat == uint32(screenshot.FormatARGB8888) || pixelFormat == uint32(screenshot.FormatXRGB8888) || pixelFormat == 0
|
|
||||||
|
var swapRB bool
|
||||||
|
switch pixelFormat {
|
||||||
|
case uint32(screenshot.FormatABGR8888), uint32(screenshot.FormatXBGR8888):
|
||||||
|
swapRB = false
|
||||||
|
default:
|
||||||
|
swapRB = true
|
||||||
|
}
|
||||||
|
|
||||||
for y := 0; y < dstH; y++ {
|
for y := 0; y < dstH; y++ {
|
||||||
srcY := int(float64(y) / scale)
|
srcY := int(float64(y) / scale)
|
||||||
@@ -309,7 +316,9 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
}
|
}
|
||||||
si := srcY*buf.Stride + srcX*4
|
si := srcY*buf.Stride + srcX*4
|
||||||
di := (y*dstW + x) * 3
|
di := (y*dstW + x) * 3
|
||||||
if si+2 < len(data) {
|
if si+3 >= len(data) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if swapRB {
|
if swapRB {
|
||||||
rgb[di+0] = data[si+2]
|
rgb[di+0] = data[si+2]
|
||||||
rgb[di+1] = data[si+1]
|
rgb[di+1] = data[si+1]
|
||||||
@@ -321,7 +330,6 @@ func bufferToRGBThumbnail(buf *screenshot.ShmBuffer, maxSize int, pixelFormat ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return rgb, dstW, dstH
|
return rgb, dstW, dstH
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,7 +378,37 @@ func runScreenshotList(cmd *cobra.Command, args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, o := range outputs {
|
for _, o := range outputs {
|
||||||
fmt.Printf("%s: %dx%d+%d+%d (scale: %d)\n",
|
scaleStr := fmt.Sprintf("%.2f", o.FractionalScale)
|
||||||
o.Name, o.Width, o.Height, o.X, o.Y, o.Scale)
|
if o.FractionalScale == float64(int(o.FractionalScale)) {
|
||||||
|
scaleStr = fmt.Sprintf("%d", int(o.FractionalScale))
|
||||||
|
}
|
||||||
|
|
||||||
|
transformStr := transformName(o.Transform)
|
||||||
|
|
||||||
|
fmt.Printf("%s: %dx%d+%d+%d scale=%s transform=%s\n",
|
||||||
|
o.Name, o.Width, o.Height, o.X, o.Y, scaleStr, transformStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func transformName(t int32) string {
|
||||||
|
switch t {
|
||||||
|
case 0:
|
||||||
|
return "normal"
|
||||||
|
case 1:
|
||||||
|
return "90"
|
||||||
|
case 2:
|
||||||
|
return "180"
|
||||||
|
case 3:
|
||||||
|
return "270"
|
||||||
|
case 4:
|
||||||
|
return "flipped"
|
||||||
|
case 5:
|
||||||
|
return "flipped-90"
|
||||||
|
case 6:
|
||||||
|
return "flipped-180"
|
||||||
|
case 7:
|
||||||
|
return "flipped-270"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("%d", t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ type WindowGeometry struct {
|
|||||||
Scale float64
|
Scale float64
|
||||||
OutputX int32
|
OutputX int32
|
||||||
OutputY int32
|
OutputY int32
|
||||||
|
OutputTransform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveWindow() (*WindowGeometry, error) {
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
@@ -385,17 +386,22 @@ func GetFocusedMonitor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOutputPosition(outputName string) (x, y int32, ok bool) {
|
type outputInfo struct {
|
||||||
|
x, y int32
|
||||||
|
transform int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func getOutputInfo(outputName string) (*outputInfo, bool) {
|
||||||
display, err := client.Connect("")
|
display, err := client.Connect("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
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 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputManager *wlr_output_management.ZwlrOutputManagerV1
|
var outputManager *wlr_output_management.ZwlrOutputManagerV1
|
||||||
@@ -414,16 +420,17 @@ func getOutputPosition(outputName string) (x, y int32, ok bool) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
return 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if outputManager == nil {
|
if outputManager == nil {
|
||||||
return 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
type headState struct {
|
type headState struct {
|
||||||
name string
|
name string
|
||||||
x, y int32
|
x, y int32
|
||||||
|
transform int32
|
||||||
}
|
}
|
||||||
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
|
heads := make(map[*wlr_output_management.ZwlrOutputHeadV1]*headState)
|
||||||
done := false
|
done := false
|
||||||
@@ -438,6 +445,9 @@ func getOutputPosition(outputName string) (x, y int32, ok bool) {
|
|||||||
state.x = pe.X
|
state.x = pe.X
|
||||||
state.y = pe.Y
|
state.y = pe.Y
|
||||||
})
|
})
|
||||||
|
e.Head.SetTransformHandler(func(te wlr_output_management.ZwlrOutputHeadV1TransformEvent) {
|
||||||
|
state.transform = te.Transform
|
||||||
|
})
|
||||||
})
|
})
|
||||||
outputManager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
|
outputManager.SetDoneHandler(func(e wlr_output_management.ZwlrOutputManagerV1DoneEvent) {
|
||||||
done = true
|
done = true
|
||||||
@@ -445,17 +455,21 @@ func getOutputPosition(outputName string) (x, y int32, ok bool) {
|
|||||||
|
|
||||||
for !done {
|
for !done {
|
||||||
if err := ctx.Dispatch(); err != nil {
|
if err := ctx.Dispatch(); err != nil {
|
||||||
return 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, state := range heads {
|
for _, state := range heads {
|
||||||
if state.name == outputName {
|
if state.name == outputName {
|
||||||
return state.x, state.y, true
|
return &outputInfo{
|
||||||
|
x: state.x,
|
||||||
|
y: state.y,
|
||||||
|
transform: state.transform,
|
||||||
|
}, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0, 0, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDWLActiveWindow() (*WindowGeometry, error) {
|
func getDWLActiveWindow() (*WindowGeometry, error) {
|
||||||
@@ -586,21 +600,22 @@ func getDWLActiveWindow() (*WindowGeometry, error) {
|
|||||||
scale = 1.0
|
scale = 1.0
|
||||||
}
|
}
|
||||||
|
|
||||||
var outputX, outputY int32
|
geom := &WindowGeometry{
|
||||||
if ox, oy, ok := getOutputPosition(state.name); ok {
|
|
||||||
outputX, outputY = ox, oy
|
|
||||||
}
|
|
||||||
|
|
||||||
return &WindowGeometry{
|
|
||||||
X: state.x,
|
X: state.x,
|
||||||
Y: state.y,
|
Y: state.y,
|
||||||
Width: state.w,
|
Width: state.w,
|
||||||
Height: state.h,
|
Height: state.h,
|
||||||
Output: state.name,
|
Output: state.name,
|
||||||
Scale: scale,
|
Scale: scale,
|
||||||
OutputX: outputX,
|
}
|
||||||
OutputY: outputY,
|
|
||||||
}, nil
|
if info, ok := getOutputInfo(state.name); ok {
|
||||||
|
geom.OutputX = info.x
|
||||||
|
geom.OutputY = info.y
|
||||||
|
geom.OutputTransform = info.transform
|
||||||
|
}
|
||||||
|
|
||||||
|
return geom, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no active output found")
|
return nil, fmt.Errorf("no active output found")
|
||||||
|
|||||||
@@ -20,7 +20,13 @@ func BufferToImageWithFormat(buf *ShmBuffer, format uint32) *image.RGBA {
|
|||||||
img := image.NewRGBA(image.Rect(0, 0, buf.Width, buf.Height))
|
img := image.NewRGBA(image.Rect(0, 0, buf.Width, buf.Height))
|
||||||
data := buf.Data()
|
data := buf.Data()
|
||||||
|
|
||||||
swapRB := format == uint32(FormatARGB8888) || format == uint32(FormatXRGB8888) || format == 0
|
var swapRB bool
|
||||||
|
switch format {
|
||||||
|
case uint32(FormatABGR8888), uint32(FormatXBGR8888):
|
||||||
|
swapRB = false
|
||||||
|
default:
|
||||||
|
swapRB = true
|
||||||
|
}
|
||||||
|
|
||||||
for y := 0; y < buf.Height; y++ {
|
for y := 0; y < buf.Height; y++ {
|
||||||
srcOff := y * buf.Stride
|
srcOff := y * buf.Stride
|
||||||
|
|||||||
@@ -380,19 +380,21 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var capturedBuf *ShmBuffer
|
||||||
|
|
||||||
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
||||||
|
if int(e.Stride) < int(e.Width)*4 {
|
||||||
|
log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width)
|
||||||
|
return
|
||||||
|
}
|
||||||
buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
buf, err := CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("create screen buffer failed", "err", err)
|
log.Error("create screen buffer failed", "err", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if withCursor {
|
capturedBuf = buf
|
||||||
pc.screenBuf = buf
|
|
||||||
pc.format = e.Format
|
pc.format = e.Format
|
||||||
} else {
|
|
||||||
pc.screenBufNoCursor = buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
|
pool, err := r.shm.CreatePool(buf.Fd(), int32(buf.Size()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -421,6 +423,34 @@ func (r *RegionSelector) preCaptureOutput(output *WaylandOutput, pc *PreCapture,
|
|||||||
|
|
||||||
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
|
||||||
frame.Destroy()
|
frame.Destroy()
|
||||||
|
|
||||||
|
if capturedBuf == nil {
|
||||||
|
onReady()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pc.yInverted {
|
||||||
|
capturedBuf.FlipVertical()
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if withCursor {
|
||||||
|
pc.screenBuf = capturedBuf
|
||||||
|
} else {
|
||||||
|
pc.screenBufNoCursor = capturedBuf
|
||||||
|
}
|
||||||
|
|
||||||
onReady()
|
onReady()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -324,13 +324,18 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
|
|||||||
|
|
||||||
outX, outY := output.x, output.y
|
outX, outY := output.x, output.y
|
||||||
scale := float64(output.scale)
|
scale := float64(output.scale)
|
||||||
if DetectCompositor() == CompositorHyprland {
|
switch DetectCompositor() {
|
||||||
|
case CompositorHyprland:
|
||||||
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
|
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
|
||||||
outX, outY = hx, hy
|
outX, outY = hx, hy
|
||||||
}
|
}
|
||||||
if s := GetHyprlandMonitorScale(output.name); s > 0 {
|
if s := GetHyprlandMonitorScale(output.name); s > 0 {
|
||||||
scale = s
|
scale = s
|
||||||
}
|
}
|
||||||
|
case CompositorDWL:
|
||||||
|
if info, ok := getOutputInfo(output.name); ok {
|
||||||
|
outX, outY = info.x, info.y
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if scale <= 0 {
|
if scale <= 0 {
|
||||||
scale = 1.0
|
scale = 1.0
|
||||||
@@ -458,13 +463,42 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult
|
|||||||
return nil, fmt.Errorf("capture output: %w", err)
|
return nil, fmt.Errorf("capture output: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.processFrame(frame, Region{
|
result, err := s.processFrame(frame, Region{
|
||||||
X: output.x,
|
X: output.x,
|
||||||
Y: output.y,
|
Y: output.y,
|
||||||
Width: output.width,
|
Width: output.width,
|
||||||
Height: output.height,
|
Height: output.height,
|
||||||
Output: output.name,
|
Output: output.name,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.YInverted {
|
||||||
|
result.Buffer.FlipVertical()
|
||||||
|
result.YInverted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if output.transform == TransformNormal {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
invTransform := InverseTransform(output.transform)
|
||||||
|
transformed, err := result.Buffer.ApplyTransform(invTransform)
|
||||||
|
if err != nil {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("apply transform: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if transformed != result.Buffer {
|
||||||
|
result.Buffer.Close()
|
||||||
|
result.Buffer = transformed
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Region.Width = int32(transformed.Width)
|
||||||
|
result.Region.Height = int32(transformed.Height)
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
@@ -545,6 +579,10 @@ func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*Ca
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
|
if output.transform != TransformNormal {
|
||||||
|
return s.captureRegionOnTransformedOutput(output, region)
|
||||||
|
}
|
||||||
|
|
||||||
scale := output.fractionalScale
|
scale := output.fractionalScale
|
||||||
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
||||||
scale = GetHyprlandMonitorScale(output.name)
|
scale = GetHyprlandMonitorScale(output.name)
|
||||||
@@ -599,6 +637,76 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
return s.processFrame(frame, region)
|
return s.processFrame(frame, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Screenshoter) captureRegionOnTransformedOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
|
||||||
|
result, err := s.captureWholeOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scale := output.fractionalScale
|
||||||
|
if scale <= 0 && DetectCompositor() == CompositorHyprland {
|
||||||
|
scale = GetHyprlandMonitorScale(output.name)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = float64(output.scale)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
localX := int(float64(region.X-output.x) * scale)
|
||||||
|
localY := int(float64(region.Y-output.y) * scale)
|
||||||
|
w := int(float64(region.Width) * scale)
|
||||||
|
h := int(float64(region.Height) * scale)
|
||||||
|
|
||||||
|
if localX < 0 {
|
||||||
|
w += localX
|
||||||
|
localX = 0
|
||||||
|
}
|
||||||
|
if localY < 0 {
|
||||||
|
h += localY
|
||||||
|
localY = 0
|
||||||
|
}
|
||||||
|
if localX+w > result.Buffer.Width {
|
||||||
|
w = result.Buffer.Width - localX
|
||||||
|
}
|
||||||
|
if localY+h > result.Buffer.Height {
|
||||||
|
h = result.Buffer.Height - localY
|
||||||
|
}
|
||||||
|
|
||||||
|
if w <= 0 || h <= 0 {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("region not visible on output")
|
||||||
|
}
|
||||||
|
|
||||||
|
cropped, err := CreateShmBuffer(w, h, w*4)
|
||||||
|
if err != nil {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("create crop buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcData := result.Buffer.Data()
|
||||||
|
dstData := cropped.Data()
|
||||||
|
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
srcOff := (localY+y)*result.Buffer.Stride + localX*4
|
||||||
|
dstOff := y * cropped.Stride
|
||||||
|
if srcOff+w*4 <= len(srcData) && dstOff+w*4 <= len(dstData) {
|
||||||
|
copy(dstData[dstOff:dstOff+w*4], srcData[srcOff:srcOff+w*4])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Buffer.Close()
|
||||||
|
cropped.Format = PixelFormat(result.Format)
|
||||||
|
|
||||||
|
return &CaptureResult{
|
||||||
|
Buffer: cropped,
|
||||||
|
Region: region,
|
||||||
|
YInverted: false,
|
||||||
|
Format: result.Format,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) {
|
func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) {
|
||||||
var buf *ShmBuffer
|
var buf *ShmBuffer
|
||||||
var pool *client.ShmPool
|
var pool *client.ShmPool
|
||||||
@@ -609,6 +717,10 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
|
|||||||
failed := false
|
failed := false
|
||||||
|
|
||||||
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
frame.SetBufferHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1BufferEvent) {
|
||||||
|
if int(e.Stride) < int(e.Width)*4 {
|
||||||
|
log.Error("invalid stride from compositor", "stride", e.Stride, "width", e.Width)
|
||||||
|
return
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
buf, err = CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
buf, err = CreateShmBuffer(int(e.Width), int(e.Height), int(e.Stride))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -906,16 +1018,32 @@ func ListOutputs() ([]Output, error) {
|
|||||||
sc.outputsMu.Lock()
|
sc.outputsMu.Lock()
|
||||||
defer sc.outputsMu.Unlock()
|
defer sc.outputsMu.Unlock()
|
||||||
|
|
||||||
|
compositor := DetectCompositor()
|
||||||
result := make([]Output, 0, len(sc.outputs))
|
result := make([]Output, 0, len(sc.outputs))
|
||||||
for _, o := range sc.outputs {
|
for _, o := range sc.outputs {
|
||||||
result = append(result, Output{
|
out := Output{
|
||||||
Name: o.name,
|
Name: o.name,
|
||||||
X: o.x,
|
X: o.x,
|
||||||
Y: o.y,
|
Y: o.y,
|
||||||
Width: o.width,
|
Width: o.width,
|
||||||
Height: o.height,
|
Height: o.height,
|
||||||
Scale: o.scale,
|
Scale: o.scale,
|
||||||
})
|
FractionalScale: o.fractionalScale,
|
||||||
|
Transform: o.transform,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch compositor {
|
||||||
|
case CompositorHyprland:
|
||||||
|
if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok {
|
||||||
|
out.X, out.Y = hx, hy
|
||||||
|
out.Width, out.Height = hw, hh
|
||||||
|
}
|
||||||
|
if s := GetHyprlandMonitorScale(o.name); s > 0 {
|
||||||
|
out.FractionalScale = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result = append(result, out)
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,23 @@ const (
|
|||||||
FormatXBGR8888 = shm.FormatXBGR8888
|
FormatXBGR8888 = shm.FormatXBGR8888
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransformNormal = shm.TransformNormal
|
||||||
|
Transform90 = shm.Transform90
|
||||||
|
Transform180 = shm.Transform180
|
||||||
|
Transform270 = shm.Transform270
|
||||||
|
TransformFlipped = shm.TransformFlipped
|
||||||
|
TransformFlipped90 = shm.TransformFlipped90
|
||||||
|
TransformFlipped180 = shm.TransformFlipped180
|
||||||
|
TransformFlipped270 = shm.TransformFlipped270
|
||||||
|
)
|
||||||
|
|
||||||
type ShmBuffer = shm.Buffer
|
type ShmBuffer = shm.Buffer
|
||||||
|
|
||||||
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
func CreateShmBuffer(width, height, stride int) (*ShmBuffer, error) {
|
||||||
return shm.CreateBuffer(width, height, stride)
|
return shm.CreateBuffer(width, height, stride)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func InverseTransform(transform int32) int32 {
|
||||||
|
return shm.InverseTransform(transform)
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ type Output struct {
|
|||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
Scale int32
|
Scale int32
|
||||||
|
FractionalScale float64
|
||||||
|
Transform int32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
|||||||
@@ -137,3 +137,96 @@ func (b *Buffer) Clear() {
|
|||||||
func (b *Buffer) CopyFrom(src *Buffer) {
|
func (b *Buffer) CopyFrom(src *Buffer) {
|
||||||
copy(b.data, src.data)
|
copy(b.data, src.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
TransformNormal = 0
|
||||||
|
Transform90 = 1
|
||||||
|
Transform180 = 2
|
||||||
|
Transform270 = 3
|
||||||
|
TransformFlipped = 4
|
||||||
|
TransformFlipped90 = 5
|
||||||
|
TransformFlipped180 = 6
|
||||||
|
TransformFlipped270 = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *Buffer) ApplyTransform(transform int32) (*Buffer, error) {
|
||||||
|
if transform == TransformNormal {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var newW, newH int
|
||||||
|
switch transform {
|
||||||
|
case Transform90, Transform270, TransformFlipped90, TransformFlipped270:
|
||||||
|
newW, newH = b.Height, b.Width
|
||||||
|
default:
|
||||||
|
newW, newH = b.Width, b.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
newBuf, err := CreateBuffer(newW, newH, newW*4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newBuf.Format = b.Format
|
||||||
|
|
||||||
|
srcData := b.data
|
||||||
|
dstData := newBuf.data
|
||||||
|
|
||||||
|
for sy := 0; sy < b.Height; sy++ {
|
||||||
|
for sx := 0; sx < b.Width; sx++ {
|
||||||
|
var dx, dy int
|
||||||
|
|
||||||
|
switch transform {
|
||||||
|
case Transform90: // 90° CCW
|
||||||
|
dx = sy
|
||||||
|
dy = b.Width - 1 - sx
|
||||||
|
case Transform180:
|
||||||
|
dx = b.Width - 1 - sx
|
||||||
|
dy = b.Height - 1 - sy
|
||||||
|
case Transform270: // 270° CCW = 90° CW
|
||||||
|
dx = b.Height - 1 - sy
|
||||||
|
dy = sx
|
||||||
|
case TransformFlipped:
|
||||||
|
dx = b.Width - 1 - sx
|
||||||
|
dy = sy
|
||||||
|
case TransformFlipped90:
|
||||||
|
dx = sy
|
||||||
|
dy = sx
|
||||||
|
case TransformFlipped180:
|
||||||
|
dx = sx
|
||||||
|
dy = b.Height - 1 - sy
|
||||||
|
case TransformFlipped270:
|
||||||
|
dx = b.Height - 1 - sy
|
||||||
|
dy = b.Width - 1 - sx
|
||||||
|
default:
|
||||||
|
dx, dy = sx, sy
|
||||||
|
}
|
||||||
|
|
||||||
|
si := sy*b.Stride + sx*4
|
||||||
|
di := dy*newBuf.Stride + dx*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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func InverseTransform(transform int32) int32 {
|
||||||
|
switch transform {
|
||||||
|
case Transform90:
|
||||||
|
return Transform270
|
||||||
|
case Transform270:
|
||||||
|
return Transform90
|
||||||
|
case TransformFlipped90:
|
||||||
|
return TransformFlipped270
|
||||||
|
case TransformFlipped270:
|
||||||
|
return TransformFlipped90
|
||||||
|
default:
|
||||||
|
return transform
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user