1251 lines
36 KiB
Go
1251 lines
36 KiB
Go
//go:build windows
|
|
|
|
package capture
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"log"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
const (
|
|
d3d11SdkVersion = 7
|
|
d3d11CreateDeviceBgraSupport = 0x20
|
|
d3dDriverTypeUnknown = 0
|
|
dxgiErrorNotFound uintptr = 0x887A0002
|
|
dxgiErrorWaitTimeout uintptr = 0x887A0027
|
|
dxgiErrorAccessLost uintptr = 0x887A0026
|
|
S_OK uintptr = 0
|
|
)
|
|
|
|
var (
|
|
dxgiDLL = syscall.NewLazyDLL("dxgi.dll")
|
|
d3d11DLL = syscall.NewLazyDLL("d3d11.dll")
|
|
procCreateDXGIFactory1 = dxgiDLL.NewProc("CreateDXGIFactory1")
|
|
procD3D11CreateDevice = d3d11DLL.NewProc("D3D11CreateDevice")
|
|
)
|
|
|
|
var (
|
|
IID_IDXGIFactory1 = windows.GUID{Data1: 0x7b7166ec, Data2: 0x21c7, Data3: 0x44ae, Data4: [8]byte{0xb2, 0x1a, 0xc9, 0xae, 0x32, 0x1a, 0xe3, 0x69}}
|
|
IID_IDXGIAdapter1 = windows.GUID{Data1: 0x29038f61, Data2: 0x3839, Data3: 0x4626, Data4: [8]byte{0x91, 0xfd, 0x08, 0x68, 0x79, 0x01, 0x1a, 0x05}}
|
|
IID_IDXGIOutput1 = windows.GUID{Data1: 0x00cddea8, Data2: 0x939b, Data3: 0x4b83, Data4: [8]byte{0xa3, 0x40, 0xa6, 0x85, 0x22, 0x66, 0x66, 0xcc}}
|
|
IID_IDXGIOutputDuplication = windows.GUID{Data1: 0x191cfac3, Data2: 0xa341, Data3: 0x470d, Data4: [8]byte{0xb2, 0x6e, 0xa8, 0x64, 0xf4, 0x28, 0x31, 0x9c}}
|
|
IID_ID3D11Device = windows.GUID{Data1: 0xdb6f6ddb, Data2: 0xac77, Data3: 0x4e88, Data4: [8]byte{0x82, 0x53, 0x81, 0x9d, 0xf9, 0xbb, 0xf1, 0x40}}
|
|
IID_ID3D11DeviceContext = windows.GUID{Data1: 0xc0bfa96c, Data2: 0xe089, Data3: 0x44fb, Data4: [8]byte{0x8e, 0xaf, 0x26, 0xf8, 0x79, 0x61, 0x90, 0xda}}
|
|
IID_ID3D11Texture2D = windows.GUID{Data1: 0x6f15aaf2, Data2: 0xd208, Data3: 0x4e89, Data4: [8]byte{0x9a, 0xb4, 0x48, 0x95, 0x35, 0xd3, 0x4f, 0x9c}}
|
|
)
|
|
|
|
const (
|
|
d3d11UsageStaging = 3
|
|
d3d11CpuAccessRead = 0x20000
|
|
d3d11MapRead = 1
|
|
dxgiModeRotationUnspecified = 0
|
|
dxgiModeRotationIdentity = 1
|
|
dxgiModeRotationRotate90 = 2
|
|
dxgiModeRotationRotate180 = 3
|
|
dxgiModeRotationRotate270 = 4
|
|
)
|
|
|
|
type iunknownVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
}
|
|
|
|
type iunknown struct {
|
|
lpVtbl *iunknownVtbl
|
|
}
|
|
|
|
func callSyscallN(fn uintptr, args ...uintptr) uintptr {
|
|
r, _, _ := syscall.SyscallN(fn, args...)
|
|
return r
|
|
}
|
|
|
|
func (u *iunknown) QueryInterface(iid *windows.GUID, obj unsafe.Pointer) uintptr {
|
|
if u == nil || u.lpVtbl == nil {
|
|
return 0
|
|
}
|
|
return callSyscallN(u.lpVtbl.QueryInterface, uintptr(unsafe.Pointer(u)), uintptr(unsafe.Pointer(iid)), uintptr(obj))
|
|
}
|
|
|
|
func (u *iunknown) Release() {
|
|
if u == nil || u.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(u.lpVtbl.Release, uintptr(unsafe.Pointer(u)))
|
|
}
|
|
|
|
type idxgiFactory1Vtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetPrivateData uintptr
|
|
GetParent uintptr
|
|
EnumAdapters uintptr
|
|
MakeWindowAssociation uintptr
|
|
GetWindowAssociation uintptr
|
|
CreateSwapChain uintptr
|
|
CreateSoftwareAdapter uintptr
|
|
EnumAdapters1 uintptr
|
|
IsCurrent uintptr
|
|
}
|
|
|
|
type idxgiFactory1 struct {
|
|
lpVtbl *idxgiFactory1Vtbl
|
|
}
|
|
|
|
func (f *idxgiFactory1) Release() {
|
|
if f == nil || f.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(f.lpVtbl.Release, uintptr(unsafe.Pointer(f)))
|
|
}
|
|
|
|
func (f *idxgiFactory1) EnumAdapters1(index uint32, adapter **idxgiAdapter1) uintptr {
|
|
return callSyscallN(f.lpVtbl.EnumAdapters1, uintptr(unsafe.Pointer(f)), uintptr(index), uintptr(unsafe.Pointer(adapter)))
|
|
}
|
|
|
|
type idxgiAdapter1Vtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetPrivateData uintptr
|
|
GetParent uintptr
|
|
EnumOutputs uintptr
|
|
GetDesc uintptr
|
|
CheckInterfaceSupport uintptr
|
|
GetDesc1 uintptr
|
|
}
|
|
|
|
type idxgiAdapter1 struct {
|
|
lpVtbl *idxgiAdapter1Vtbl
|
|
}
|
|
|
|
func (a *idxgiAdapter1) Release() {
|
|
if a == nil || a.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(a.lpVtbl.Release, uintptr(unsafe.Pointer(a)))
|
|
}
|
|
|
|
func (a *idxgiAdapter1) EnumOutputs(index uint32, output **idxgiOutput) uintptr {
|
|
return callSyscallN(a.lpVtbl.EnumOutputs, uintptr(unsafe.Pointer(a)), uintptr(index), uintptr(unsafe.Pointer(output)))
|
|
}
|
|
|
|
type dxgiOutputDesc struct {
|
|
DeviceName [32]uint16
|
|
DesktopCoordinates rect
|
|
AttachedToDesktop int32
|
|
Rotation uint32
|
|
Monitor uintptr
|
|
}
|
|
|
|
type idxgiOutputVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetPrivateData uintptr
|
|
GetParent uintptr
|
|
GetDesc uintptr
|
|
GetDisplayModeList uintptr
|
|
FindClosestMatchingMode uintptr
|
|
WaitForVBlank uintptr
|
|
TakeOwnership uintptr
|
|
ReleaseOwnership uintptr
|
|
GetGammaControlCapabilities uintptr
|
|
SetGammaControl uintptr
|
|
GetGammaControl uintptr
|
|
SetDisplaySurface uintptr
|
|
GetDisplaySurfaceData uintptr
|
|
GetFrameStatistics uintptr
|
|
}
|
|
|
|
type idxgiOutput struct {
|
|
lpVtbl *idxgiOutputVtbl
|
|
}
|
|
|
|
func (o *idxgiOutput) Release() {
|
|
if o == nil || o.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(o.lpVtbl.Release, uintptr(unsafe.Pointer(o)))
|
|
}
|
|
|
|
func (o *idxgiOutput) GetDesc(desc *dxgiOutputDesc) uintptr {
|
|
return callSyscallN(o.lpVtbl.GetDesc, uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(desc)))
|
|
}
|
|
|
|
type idxgiOutput1Vtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetPrivateData uintptr
|
|
GetParent uintptr
|
|
GetDesc uintptr
|
|
GetDisplayModeList uintptr
|
|
FindClosestMatchingMode uintptr
|
|
WaitForVBlank uintptr
|
|
TakeOwnership uintptr
|
|
ReleaseOwnership uintptr
|
|
GetGammaControlCapabilities uintptr
|
|
SetGammaControl uintptr
|
|
GetGammaControl uintptr
|
|
SetDisplaySurface uintptr
|
|
GetDisplaySurfaceData uintptr
|
|
GetFrameStatistics uintptr
|
|
GetDisplayModeList1 uintptr
|
|
FindClosestMatchingMode1 uintptr
|
|
GetDisplaySurfaceData1 uintptr
|
|
DuplicateOutput uintptr
|
|
}
|
|
|
|
type idxgiOutput1 struct {
|
|
lpVtbl *idxgiOutput1Vtbl
|
|
}
|
|
|
|
func (o *idxgiOutput1) Release() {
|
|
if o == nil || o.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(o.lpVtbl.Release, uintptr(unsafe.Pointer(o)))
|
|
}
|
|
|
|
func (o *idxgiOutput1) DuplicateOutput(device *iunknown, dup **idxgiOutputDuplication) uintptr {
|
|
return callSyscallN(o.lpVtbl.DuplicateOutput, uintptr(unsafe.Pointer(o)), uintptr(unsafe.Pointer(device)), uintptr(unsafe.Pointer(dup)))
|
|
}
|
|
|
|
type dxgiRational struct {
|
|
Numerator uint32
|
|
Denominator uint32
|
|
}
|
|
|
|
type dxgiModeDesc struct {
|
|
Width uint32
|
|
Height uint32
|
|
RefreshRate dxgiRational
|
|
Format uint32
|
|
ScanlineOrdering uint32
|
|
Scaling uint32
|
|
}
|
|
|
|
type dxgiOutDuplDesc struct {
|
|
ModeDesc dxgiModeDesc
|
|
Rotation uint32
|
|
DesktopImageInSystemMemory int32
|
|
}
|
|
|
|
type dxgiOutDuplPointerPosition struct {
|
|
Position point
|
|
Visible int32
|
|
}
|
|
|
|
type dxgiOutDuplFrameInfo struct {
|
|
LastPresentTime uint64
|
|
LastMouseUpdateTime uint64
|
|
AccumulatedFrames uint32
|
|
RectsCoalesced uint32
|
|
ProtectedContentMaskedOut uint32
|
|
PointerPosition dxgiOutDuplPointerPosition
|
|
TotalMetadataBufferSize uint32
|
|
PointerShapeBufferSize uint32
|
|
}
|
|
|
|
type dxgiMappedRect struct {
|
|
Pitch int32
|
|
Bits *byte
|
|
}
|
|
|
|
type idxgiOutputDuplicationVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetPrivateData uintptr
|
|
GetParent uintptr
|
|
GetDesc uintptr
|
|
AcquireNextFrame uintptr
|
|
GetFrameDirtyRects uintptr
|
|
GetFrameMoveRects uintptr
|
|
GetFramePointerShape uintptr
|
|
MapDesktopSurface uintptr
|
|
UnMapDesktopSurface uintptr
|
|
ReleaseFrame uintptr
|
|
}
|
|
|
|
type idxgiOutputDuplication struct {
|
|
lpVtbl *idxgiOutputDuplicationVtbl
|
|
}
|
|
|
|
type d3d11DeviceVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
CreateBuffer uintptr
|
|
CreateTexture1D uintptr
|
|
CreateTexture2D uintptr
|
|
CreateTexture3D uintptr
|
|
CreateShaderResourceView uintptr
|
|
CreateUnorderedAccessView uintptr
|
|
CreateRenderTargetView uintptr
|
|
CreateDepthStencilView uintptr
|
|
CreateInputLayout uintptr
|
|
CreateVertexShader uintptr
|
|
CreateGeometryShader uintptr
|
|
CreateGeometryShaderWithStreamOutput uintptr
|
|
CreatePixelShader uintptr
|
|
CreateHullShader uintptr
|
|
CreateDomainShader uintptr
|
|
CreateComputeShader uintptr
|
|
CreateClassLinkage uintptr
|
|
CreateBlendState uintptr
|
|
CreateDepthStencilState uintptr
|
|
CreateRasterizerState uintptr
|
|
CreateSamplerState uintptr
|
|
CreateQuery uintptr
|
|
CreatePredicate uintptr
|
|
CreateCounter uintptr
|
|
CreateDeferredContext uintptr
|
|
OpenSharedResource uintptr
|
|
CheckFormatSupport uintptr
|
|
CheckMultisampleQualityLevels uintptr
|
|
CheckCounterInfo uintptr
|
|
CheckCounter uintptr
|
|
CheckFeatureSupport uintptr
|
|
GetPrivateData uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetFeatureLevel uintptr
|
|
GetCreationFlags uintptr
|
|
GetDeviceRemovedReason uintptr
|
|
GetImmediateContext uintptr
|
|
SetExceptionMode uintptr
|
|
GetExceptionMode uintptr
|
|
}
|
|
|
|
type d3d11Device struct {
|
|
lpVtbl *d3d11DeviceVtbl
|
|
}
|
|
|
|
func (d *d3d11Device) Release() {
|
|
if d == nil || d.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(d.lpVtbl.Release, uintptr(unsafe.Pointer(d)))
|
|
}
|
|
|
|
func (d *d3d11Device) GetImmediateContext(ctx **d3d11DeviceContext) {
|
|
if d == nil || d.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(d.lpVtbl.GetImmediateContext, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(ctx)))
|
|
}
|
|
|
|
func (d *d3d11Device) CreateTexture2D(desc *d3d11Texture2DDesc, initialData unsafe.Pointer, tex **d3d11Texture2D) uintptr {
|
|
return callSyscallN(d.lpVtbl.CreateTexture2D, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(desc)), uintptr(initialData), uintptr(unsafe.Pointer(tex)))
|
|
}
|
|
|
|
type d3d11DeviceContextVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
GetDevice uintptr
|
|
GetPrivateData uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
VSSetConstantBuffers uintptr
|
|
PSSetShaderResources uintptr
|
|
PSSetShader uintptr
|
|
PSSetSamplers uintptr
|
|
VSSetShader uintptr
|
|
DrawIndexed uintptr
|
|
Draw uintptr
|
|
Map uintptr
|
|
Unmap uintptr
|
|
PSSetConstantBuffers uintptr
|
|
IASetInputLayout uintptr
|
|
IASetVertexBuffers uintptr
|
|
IASetIndexBuffer uintptr
|
|
DrawIndexedInstanced uintptr
|
|
DrawInstanced uintptr
|
|
GSSetConstantBuffers uintptr
|
|
GSSetShader uintptr
|
|
IASetPrimitiveTopology uintptr
|
|
VSSetShaderResources uintptr
|
|
VSSetSamplers uintptr
|
|
Begin uintptr
|
|
End uintptr
|
|
GetData uintptr
|
|
SetPredication uintptr
|
|
GSSetShaderResources uintptr
|
|
GSSetSamplers uintptr
|
|
OMSetRenderTargets uintptr
|
|
OMSetRenderTargetsAndUnorderedAccessViews uintptr
|
|
OMSetBlendState uintptr
|
|
OMSetDepthStencilState uintptr
|
|
SOSetTargets uintptr
|
|
DrawAuto uintptr
|
|
DrawIndexedInstancedIndirect uintptr
|
|
DrawInstancedIndirect uintptr
|
|
Dispatch uintptr
|
|
DispatchIndirect uintptr
|
|
RSSetState uintptr
|
|
RSSetViewports uintptr
|
|
RSSetScissorRects uintptr
|
|
CopySubresourceRegion uintptr
|
|
CopyResource uintptr
|
|
UpdateSubresource uintptr
|
|
CopyStructureCount uintptr
|
|
ClearRenderTargetView uintptr
|
|
ClearUnorderedAccessViewUint uintptr
|
|
ClearUnorderedAccessViewFloat uintptr
|
|
ClearDepthStencilView uintptr
|
|
GenerateMips uintptr
|
|
SetResourceMinLOD uintptr
|
|
GetResourceMinLOD uintptr
|
|
ResolveSubresource uintptr
|
|
ExecuteCommandList uintptr
|
|
HSSetShaderResources uintptr
|
|
HSSetShader uintptr
|
|
HSSetSamplers uintptr
|
|
HSSetConstantBuffers uintptr
|
|
DSSetShaderResources uintptr
|
|
DSSetShader uintptr
|
|
DSSetSamplers uintptr
|
|
DSSetConstantBuffers uintptr
|
|
CSSetShaderResources uintptr
|
|
CSSetUnorderedAccessViews uintptr
|
|
CSSetShader uintptr
|
|
CSSetSamplers uintptr
|
|
CSSetConstantBuffers uintptr
|
|
VSGetConstantBuffers uintptr
|
|
PSGetShaderResources uintptr
|
|
PSGetShader uintptr
|
|
PSGetSamplers uintptr
|
|
VSGetShader uintptr
|
|
PSGetConstantBuffers uintptr
|
|
IAGetInputLayout uintptr
|
|
IAGetVertexBuffers uintptr
|
|
IAGetIndexBuffer uintptr
|
|
GSGetConstantBuffers uintptr
|
|
GSGetShader uintptr
|
|
IAGetPrimitiveTopology uintptr
|
|
VSGetShaderResources uintptr
|
|
VSGetSamplers uintptr
|
|
GetPredication uintptr
|
|
GSGetShaderResources uintptr
|
|
GSGetSamplers uintptr
|
|
OMGetRenderTargets uintptr
|
|
OMGetRenderTargetsAndUnorderedAccessViews uintptr
|
|
OMGetBlendState uintptr
|
|
OMGetDepthStencilState uintptr
|
|
SOGetTargets uintptr
|
|
RSGetState uintptr
|
|
RSGetViewports uintptr
|
|
RSGetScissorRects uintptr
|
|
HSGetShaderResources uintptr
|
|
HSGetShader uintptr
|
|
HSGetSamplers uintptr
|
|
HSGetConstantBuffers uintptr
|
|
DSGetShaderResources uintptr
|
|
DSGetShader uintptr
|
|
DSGetSamplers uintptr
|
|
DSGetConstantBuffers uintptr
|
|
CSGetShaderResources uintptr
|
|
CSGetUnorderedAccessViews uintptr
|
|
CSGetShader uintptr
|
|
CSGetSamplers uintptr
|
|
CSGetConstantBuffers uintptr
|
|
ClearState uintptr
|
|
Flush uintptr
|
|
}
|
|
|
|
type d3d11DeviceContext struct {
|
|
lpVtbl *d3d11DeviceContextVtbl
|
|
}
|
|
|
|
func (c *d3d11DeviceContext) Release() {
|
|
if c == nil || c.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(c.lpVtbl.Release, uintptr(unsafe.Pointer(c)))
|
|
}
|
|
|
|
func (c *d3d11DeviceContext) CopyResource(dst, src *d3d11Texture2D) {
|
|
if c == nil || c.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(c.lpVtbl.CopyResource, uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(dst)), uintptr(unsafe.Pointer(src)))
|
|
}
|
|
|
|
func (c *d3d11DeviceContext) Map(res *d3d11Texture2D, subresource uint32, mapType uint32, mapFlags uint32, mapped *d3d11MappedSubresource) uintptr {
|
|
return callSyscallN(c.lpVtbl.Map, uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(res)), uintptr(subresource), uintptr(mapType), uintptr(mapFlags), uintptr(unsafe.Pointer(mapped)))
|
|
}
|
|
|
|
func (c *d3d11DeviceContext) Unmap(res *d3d11Texture2D, subresource uint32) {
|
|
if c == nil || c.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(c.lpVtbl.Unmap, uintptr(unsafe.Pointer(c)), uintptr(unsafe.Pointer(res)), uintptr(subresource))
|
|
}
|
|
|
|
type d3d11Texture2DVtbl struct {
|
|
QueryInterface uintptr
|
|
AddRef uintptr
|
|
Release uintptr
|
|
GetDevice uintptr
|
|
GetPrivateData uintptr
|
|
SetPrivateData uintptr
|
|
SetPrivateDataInterface uintptr
|
|
GetType uintptr
|
|
SetEvictionPriority uintptr
|
|
GetEvictionPriority uintptr
|
|
GetDesc uintptr
|
|
}
|
|
|
|
type d3d11Texture2D struct {
|
|
lpVtbl *d3d11Texture2DVtbl
|
|
}
|
|
|
|
func (t *d3d11Texture2D) Release() {
|
|
if t == nil || t.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(t.lpVtbl.Release, uintptr(unsafe.Pointer(t)))
|
|
}
|
|
|
|
func (t *d3d11Texture2D) GetDesc(desc *d3d11Texture2DDesc) {
|
|
if t == nil || t.lpVtbl == nil {
|
|
return
|
|
}
|
|
callSyscallN(t.lpVtbl.GetDesc, uintptr(unsafe.Pointer(t)), uintptr(unsafe.Pointer(desc)))
|
|
}
|
|
|
|
type d3d11Texture2DDesc struct {
|
|
Width uint32
|
|
Height uint32
|
|
MipLevels uint32
|
|
ArraySize uint32
|
|
Format uint32
|
|
SampleDesc struct {
|
|
Count uint32
|
|
Quality uint32
|
|
}
|
|
Usage uint32
|
|
BindFlags uint32
|
|
CPUAccessFlags uint32
|
|
MiscFlags uint32
|
|
}
|
|
|
|
type d3d11MappedSubresource struct {
|
|
Data unsafe.Pointer
|
|
RowPitch uint32
|
|
DepthPitch uint32
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) Release() {
|
|
if d == nil || d.lpVtbl == nil {
|
|
return
|
|
}
|
|
syscall.SyscallN(d.lpVtbl.Release, uintptr(unsafe.Pointer(d)))
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) GetDesc(desc *dxgiOutDuplDesc) uintptr {
|
|
return callSyscallN(d.lpVtbl.GetDesc, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(desc)))
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) AcquireNextFrame(timeoutMs uint32, info *dxgiOutDuplFrameInfo, resource **iunknown) uintptr {
|
|
return callSyscallN(d.lpVtbl.AcquireNextFrame, uintptr(unsafe.Pointer(d)), uintptr(timeoutMs), uintptr(unsafe.Pointer(info)), uintptr(unsafe.Pointer(resource)))
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) MapDesktopSurface(mapped *dxgiMappedRect) uintptr {
|
|
return callSyscallN(d.lpVtbl.MapDesktopSurface, uintptr(unsafe.Pointer(d)), uintptr(unsafe.Pointer(mapped)))
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) UnMapDesktopSurface() uintptr {
|
|
return callSyscallN(d.lpVtbl.UnMapDesktopSurface, uintptr(unsafe.Pointer(d)))
|
|
}
|
|
|
|
func (d *idxgiOutputDuplication) ReleaseFrame() uintptr {
|
|
return callSyscallN(d.lpVtbl.ReleaseFrame, uintptr(unsafe.Pointer(d)))
|
|
}
|
|
|
|
var (
|
|
desktopDuplicationEnabled atomic.Bool
|
|
dxgiState = &duplicationState{}
|
|
)
|
|
|
|
type duplicationState struct {
|
|
mu sync.Mutex
|
|
display int
|
|
outputName string
|
|
bounds image.Rectangle
|
|
cursorBounds image.Rectangle
|
|
factory *idxgiFactory1
|
|
adapter *idxgiAdapter1
|
|
output *idxgiOutput
|
|
output1 *idxgiOutput1
|
|
dup *idxgiOutputDuplication
|
|
device *d3d11Device
|
|
context *d3d11DeviceContext
|
|
staging *d3d11Texture2D
|
|
stagingDesc d3d11Texture2DDesc
|
|
desc dxgiOutDuplDesc
|
|
lastBase *image.RGBA
|
|
lastFrame *image.RGBA
|
|
lastFrameAt time.Time
|
|
lastFail time.Time
|
|
cursorScratch *image.RGBA
|
|
}
|
|
|
|
func SetDesktopDuplication(enabled bool) {
|
|
desktopDuplicationEnabled.Store(enabled)
|
|
dxgiState.reset()
|
|
}
|
|
|
|
func useDesktopDuplication() bool {
|
|
return desktopDuplicationEnabled.Load()
|
|
}
|
|
|
|
func captureDisplayDXGI(display int) (*image.RGBA, error) {
|
|
return dxgiState.capture(display)
|
|
}
|
|
|
|
func (s *duplicationState) reset() {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.closeLocked()
|
|
}
|
|
|
|
func (s *duplicationState) closeLocked() {
|
|
if s.dup != nil {
|
|
s.dup.Release()
|
|
s.dup = nil
|
|
}
|
|
if s.staging != nil {
|
|
s.staging.Release()
|
|
s.staging = nil
|
|
}
|
|
if s.context != nil {
|
|
s.context.Release()
|
|
s.context = nil
|
|
}
|
|
if s.output1 != nil {
|
|
s.output1.Release()
|
|
s.output1 = nil
|
|
}
|
|
if s.output != nil {
|
|
s.output.Release()
|
|
s.output = nil
|
|
}
|
|
if s.adapter != nil {
|
|
s.adapter.Release()
|
|
s.adapter = nil
|
|
}
|
|
if s.factory != nil {
|
|
s.factory.Release()
|
|
s.factory = nil
|
|
}
|
|
if s.device != nil {
|
|
s.device.Release()
|
|
s.device = nil
|
|
}
|
|
s.outputName = ""
|
|
s.bounds = image.Rectangle{}
|
|
s.cursorBounds = image.Rectangle{}
|
|
s.display = -1
|
|
s.lastBase = nil
|
|
s.lastFrame = nil
|
|
s.lastFrameAt = time.Time{}
|
|
s.cursorScratch = nil
|
|
}
|
|
|
|
func (s *duplicationState) capture(display int) (*image.RGBA, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
if err := s.ensure(display); err != nil {
|
|
s.lastFail = time.Now()
|
|
return nil, err
|
|
}
|
|
|
|
var info dxgiOutDuplFrameInfo
|
|
var resource *iunknown
|
|
hr := s.dup.AcquireNextFrame(5, &info, &resource)
|
|
if hr == dxgiErrorWaitTimeout {
|
|
if s.lastBase != nil {
|
|
img := s.composeFrame(s.lastBase)
|
|
s.lastFrame = img
|
|
s.lastFrameAt = time.Now()
|
|
return img, nil
|
|
}
|
|
if s.lastFrame != nil {
|
|
return s.lastFrame, nil
|
|
}
|
|
return nil, errors.New("dxgi: frame timeout")
|
|
}
|
|
if hr == dxgiErrorAccessLost {
|
|
s.closeLocked()
|
|
return nil, errors.New("dxgi: access lost")
|
|
}
|
|
if hr != S_OK {
|
|
return nil, fmt.Errorf("dxgi: acquire frame failed 0x%x", hr)
|
|
}
|
|
if resource != nil {
|
|
resource.Release()
|
|
}
|
|
|
|
width := int(s.desc.ModeDesc.Width)
|
|
height := int(s.desc.ModeDesc.Height)
|
|
if width <= 0 || height <= 0 {
|
|
_ = s.dup.ReleaseFrame()
|
|
return nil, errors.New("dxgi: invalid frame size")
|
|
}
|
|
|
|
var img *image.RGBA
|
|
if s.desc.DesktopImageInSystemMemory != 0 {
|
|
var mapped dxgiMappedRect
|
|
hr = s.dup.MapDesktopSurface(&mapped)
|
|
if hr != S_OK {
|
|
_ = s.dup.ReleaseFrame()
|
|
return nil, fmt.Errorf("dxgi: map desktop surface failed 0x%x", hr)
|
|
}
|
|
if mapped.Pitch <= 0 || mapped.Bits == nil {
|
|
_ = s.dup.UnMapDesktopSurface()
|
|
_ = s.dup.ReleaseFrame()
|
|
return nil, errors.New("dxgi: invalid mapped surface")
|
|
}
|
|
|
|
pitch := int(mapped.Pitch)
|
|
src := unsafe.Slice(mapped.Bits, pitch*height)
|
|
img = convertBGRA(src, pitch, width, height, s.desc.Rotation)
|
|
_ = s.dup.UnMapDesktopSurface()
|
|
_ = s.dup.ReleaseFrame()
|
|
} else {
|
|
img, hr = s.readbackFrame(width, height, s.desc.Rotation, resource)
|
|
_ = s.dup.ReleaseFrame()
|
|
if hr != S_OK || img == nil {
|
|
return nil, fmt.Errorf("dxgi: staging readback failed 0x%x", hr)
|
|
}
|
|
}
|
|
|
|
s.lastBase = img
|
|
img = s.composeFrame(img)
|
|
s.lastFrame = img
|
|
s.lastFrameAt = time.Now()
|
|
|
|
return img, nil
|
|
}
|
|
|
|
func (s *duplicationState) composeFrame(base *image.RGBA) *image.RGBA {
|
|
if base == nil {
|
|
return nil
|
|
}
|
|
userScale := captureScale()
|
|
withCursor := cursorCaptureEnabled.Load()
|
|
|
|
// Fast path: no cursor overlay and no scaling means we can reuse the raw
|
|
// frame without an extra clone/copy.
|
|
if !withCursor && userScale == 1 {
|
|
return base
|
|
}
|
|
|
|
img := base
|
|
if withCursor {
|
|
img = s.ensureCursorScratch(base.Rect)
|
|
copy(img.Pix, base.Pix)
|
|
}
|
|
bounds := s.bounds
|
|
if bounds.Dx() != int(s.desc.ModeDesc.Width) || bounds.Dy() != int(s.desc.ModeDesc.Height) {
|
|
bounds = image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Min.X+int(s.desc.ModeDesc.Width), bounds.Min.Y+int(s.desc.ModeDesc.Height))
|
|
}
|
|
if withCursor {
|
|
drawCursorRotated(img, s.cursorBounds, bounds, s.desc.Rotation)
|
|
}
|
|
|
|
rotW := img.Bounds().Dx()
|
|
rotH := img.Bounds().Dy()
|
|
dstW := int(float64(rotW) * userScale)
|
|
dstH := int(float64(rotH) * userScale)
|
|
if dstW > 0 && dstH > 0 && (dstW != rotW || dstH != rotH) {
|
|
img = resizeNearest(img, dstW, dstH)
|
|
}
|
|
return img
|
|
}
|
|
|
|
func (s *duplicationState) ensureCursorScratch(rect image.Rectangle) *image.RGBA {
|
|
if s.cursorScratch == nil || s.cursorScratch.Rect != rect {
|
|
s.cursorScratch = image.NewRGBA(rect)
|
|
}
|
|
return s.cursorScratch
|
|
}
|
|
|
|
func cloneRGBA(src *image.RGBA) *image.RGBA {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
dst := image.NewRGBA(src.Rect)
|
|
copy(dst.Pix, src.Pix)
|
|
return dst
|
|
}
|
|
|
|
func (s *duplicationState) ensure(display int) error {
|
|
if s.dup != nil && s.display == display {
|
|
return nil
|
|
}
|
|
s.closeLocked()
|
|
|
|
monitors := monitorList()
|
|
if display < 0 || display >= len(monitors) {
|
|
display = 0
|
|
}
|
|
if len(monitors) == 0 {
|
|
return errors.New("dxgi: no monitors")
|
|
}
|
|
|
|
mon := monitors[display]
|
|
desiredName := strings.TrimSpace(mon.name)
|
|
bounds := resolveBounds(mon)
|
|
cursorBounds := mon.rect
|
|
|
|
factory, err := createDXGIFactory1()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
output, adapter, outputName, err := findOutput(factory, desiredName, bounds, display)
|
|
if err != nil {
|
|
factory.Release()
|
|
return err
|
|
}
|
|
|
|
device, context, err := createD3DDevice(adapter)
|
|
if err != nil {
|
|
output.Release()
|
|
adapter.Release()
|
|
factory.Release()
|
|
return err
|
|
}
|
|
|
|
var output1 *idxgiOutput1
|
|
hr := (*iunknown)(unsafe.Pointer(output)).QueryInterface(&IID_IDXGIOutput1, unsafe.Pointer(&output1))
|
|
if hr != S_OK || output1 == nil {
|
|
device.Release()
|
|
output.Release()
|
|
adapter.Release()
|
|
factory.Release()
|
|
return errors.New("dxgi: failed to query IDXGIOutput1")
|
|
}
|
|
|
|
var dup *idxgiOutputDuplication
|
|
hr = output1.DuplicateOutput((*iunknown)(unsafe.Pointer(device)), &dup)
|
|
if hr != S_OK || dup == nil {
|
|
output1.Release()
|
|
context.Release()
|
|
device.Release()
|
|
output.Release()
|
|
adapter.Release()
|
|
factory.Release()
|
|
return fmt.Errorf("dxgi: DuplicateOutput failed 0x%x", hr)
|
|
}
|
|
|
|
var desc dxgiOutDuplDesc
|
|
_ = dup.GetDesc(&desc)
|
|
if desc.DesktopImageInSystemMemory == 0 {
|
|
log.Printf("capture: dxgi duplication has no system-memory surface; using staging readback")
|
|
}
|
|
|
|
s.factory = factory
|
|
s.adapter = adapter
|
|
s.output = output
|
|
s.output1 = output1
|
|
s.device = device
|
|
s.context = context
|
|
s.dup = dup
|
|
s.desc = desc
|
|
s.outputName = outputName
|
|
s.display = display
|
|
s.bounds = bounds
|
|
s.cursorBounds = cursorBounds
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *duplicationState) readbackFrame(width, height int, rotation uint32, resource *iunknown) (*image.RGBA, uintptr) {
|
|
if resource == nil || s.device == nil || s.context == nil {
|
|
return nil, uintptr(1)
|
|
}
|
|
var tex *d3d11Texture2D
|
|
hr := resource.QueryInterface(&IID_ID3D11Texture2D, unsafe.Pointer(&tex))
|
|
if hr != S_OK || tex == nil {
|
|
return nil, hr
|
|
}
|
|
defer tex.Release()
|
|
|
|
var srcDesc d3d11Texture2DDesc
|
|
tex.GetDesc(&srcDesc)
|
|
if srcDesc.Width == 0 || srcDesc.Height == 0 {
|
|
return nil, uintptr(1)
|
|
}
|
|
|
|
if s.staging == nil || s.stagingDesc.Width != srcDesc.Width || s.stagingDesc.Height != srcDesc.Height || s.stagingDesc.Format != srcDesc.Format {
|
|
if s.staging != nil {
|
|
s.staging.Release()
|
|
s.staging = nil
|
|
}
|
|
stagingDesc := srcDesc
|
|
stagingDesc.Usage = d3d11UsageStaging
|
|
stagingDesc.BindFlags = 0
|
|
stagingDesc.CPUAccessFlags = d3d11CpuAccessRead
|
|
stagingDesc.MiscFlags = 0
|
|
stagingDesc.MipLevels = 1
|
|
stagingDesc.ArraySize = 1
|
|
stagingDesc.SampleDesc.Count = 1
|
|
stagingDesc.SampleDesc.Quality = 0
|
|
var staging *d3d11Texture2D
|
|
hr = s.device.CreateTexture2D(&stagingDesc, nil, &staging)
|
|
if hr != S_OK || staging == nil {
|
|
return nil, hr
|
|
}
|
|
s.staging = staging
|
|
s.stagingDesc = stagingDesc
|
|
}
|
|
|
|
s.context.CopyResource(s.staging, tex)
|
|
var mapped d3d11MappedSubresource
|
|
hr = s.context.Map(s.staging, 0, d3d11MapRead, 0, &mapped)
|
|
if hr != S_OK || mapped.Data == nil || mapped.RowPitch == 0 {
|
|
return nil, hr
|
|
}
|
|
defer s.context.Unmap(s.staging, 0)
|
|
|
|
pitch := int(mapped.RowPitch)
|
|
src := unsafe.Slice((*byte)(mapped.Data), pitch*int(srcDesc.Height))
|
|
img := convertBGRA(src, pitch, int(srcDesc.Width), int(srcDesc.Height), rotation)
|
|
|
|
return img, S_OK
|
|
}
|
|
|
|
func createDXGIFactory1() (*idxgiFactory1, error) {
|
|
var factory *idxgiFactory1
|
|
hr, _, _ := procCreateDXGIFactory1.Call(uintptr(unsafe.Pointer(&IID_IDXGIFactory1)), uintptr(unsafe.Pointer(&factory)))
|
|
if hr != S_OK || factory == nil {
|
|
return nil, fmt.Errorf("dxgi: CreateDXGIFactory1 failed 0x%x", hr)
|
|
}
|
|
return factory, nil
|
|
}
|
|
|
|
func findOutput(factory *idxgiFactory1, desiredName string, desiredBounds image.Rectangle, displayIndex int) (*idxgiOutput, *idxgiAdapter1, string, error) {
|
|
var fallbackOutput *idxgiOutput
|
|
var fallbackAdapter *idxgiAdapter1
|
|
fallbackName := ""
|
|
outIndex := 0
|
|
|
|
for adapterIndex := 0; ; adapterIndex++ {
|
|
var adapter *idxgiAdapter1
|
|
hr := factory.EnumAdapters1(uint32(adapterIndex), &adapter)
|
|
if hr == dxgiErrorNotFound {
|
|
break
|
|
}
|
|
if hr != S_OK || adapter == nil {
|
|
continue
|
|
}
|
|
|
|
for outputIndex := 0; ; outputIndex++ {
|
|
var output *idxgiOutput
|
|
hrOut := adapter.EnumOutputs(uint32(outputIndex), &output)
|
|
if hrOut == dxgiErrorNotFound {
|
|
break
|
|
}
|
|
if hrOut != S_OK || output == nil {
|
|
continue
|
|
}
|
|
|
|
var desc dxgiOutputDesc
|
|
_ = output.GetDesc(&desc)
|
|
name := windows.UTF16ToString(desc.DeviceName[:])
|
|
coords := image.Rect(int(desc.DesktopCoordinates.left), int(desc.DesktopCoordinates.top), int(desc.DesktopCoordinates.right), int(desc.DesktopCoordinates.bottom))
|
|
|
|
if desiredBounds.Dx() > 0 && desiredBounds.Dy() > 0 && coords == desiredBounds {
|
|
if fallbackOutput != nil && fallbackAdapter != nil {
|
|
fallbackOutput.Release()
|
|
fallbackAdapter.Release()
|
|
}
|
|
return output, adapter, name, nil
|
|
}
|
|
|
|
if desiredName != "" && strings.EqualFold(strings.TrimSpace(name), desiredName) {
|
|
if fallbackOutput != nil && fallbackAdapter != nil {
|
|
fallbackOutput.Release()
|
|
fallbackAdapter.Release()
|
|
}
|
|
return output, adapter, name, nil
|
|
}
|
|
|
|
if outIndex == displayIndex && fallbackOutput == nil {
|
|
fallbackOutput = output
|
|
fallbackAdapter = adapter
|
|
fallbackName = name
|
|
} else {
|
|
output.Release()
|
|
}
|
|
outIndex++
|
|
}
|
|
if fallbackOutput == nil {
|
|
adapter.Release()
|
|
}
|
|
}
|
|
|
|
if fallbackOutput != nil && fallbackAdapter != nil {
|
|
return fallbackOutput, fallbackAdapter, fallbackName, nil
|
|
}
|
|
|
|
return nil, nil, "", errors.New("dxgi: output not found")
|
|
}
|
|
|
|
func createD3DDevice(adapter *idxgiAdapter1) (*d3d11Device, *d3d11DeviceContext, error) {
|
|
var device *d3d11Device
|
|
var context *d3d11DeviceContext
|
|
var featureLevel uint32
|
|
hr, _, _ := procD3D11CreateDevice.Call(
|
|
uintptr(unsafe.Pointer(adapter)),
|
|
uintptr(d3dDriverTypeUnknown),
|
|
0,
|
|
uintptr(d3d11CreateDeviceBgraSupport),
|
|
0,
|
|
0,
|
|
uintptr(d3d11SdkVersion),
|
|
uintptr(unsafe.Pointer(&device)),
|
|
uintptr(unsafe.Pointer(&featureLevel)),
|
|
uintptr(unsafe.Pointer(&context)),
|
|
)
|
|
if hr != S_OK || device == nil || context == nil {
|
|
if context != nil {
|
|
context.Release()
|
|
}
|
|
if device != nil {
|
|
device.Release()
|
|
}
|
|
return nil, nil, fmt.Errorf("dxgi: D3D11CreateDevice failed 0x%x", hr)
|
|
}
|
|
return device, context, nil
|
|
}
|
|
|
|
func convertBGRA(src []byte, pitch, width, height int, rotation uint32) *image.RGBA {
|
|
switch rotation {
|
|
case dxgiModeRotationRotate90:
|
|
dst := image.NewRGBA(image.Rect(0, 0, height, width))
|
|
for y := 0; y < height; y++ {
|
|
sRow := src[y*pitch : y*pitch+width*4]
|
|
for x := 0; x < width; x++ {
|
|
si := x * 4
|
|
dx := height - 1 - y
|
|
dy := x
|
|
di := dy*dst.Stride + dx*4
|
|
dst.Pix[di+0] = sRow[si+2]
|
|
dst.Pix[di+1] = sRow[si+1]
|
|
dst.Pix[di+2] = sRow[si+0]
|
|
dst.Pix[di+3] = 255
|
|
}
|
|
}
|
|
return dst
|
|
case dxgiModeRotationRotate180:
|
|
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
for y := 0; y < height; y++ {
|
|
sRow := src[y*pitch : y*pitch+width*4]
|
|
dy := height - 1 - y
|
|
dRow := dst.Pix[dy*dst.Stride : dy*dst.Stride+width*4]
|
|
for x := 0; x < width; x++ {
|
|
si := x * 4
|
|
dx := width - 1 - x
|
|
di := dx * 4
|
|
dRow[di+0] = sRow[si+2]
|
|
dRow[di+1] = sRow[si+1]
|
|
dRow[di+2] = sRow[si+0]
|
|
dRow[di+3] = 255
|
|
}
|
|
}
|
|
return dst
|
|
case dxgiModeRotationRotate270:
|
|
dst := image.NewRGBA(image.Rect(0, 0, height, width))
|
|
for y := 0; y < height; y++ {
|
|
sRow := src[y*pitch : y*pitch+width*4]
|
|
for x := 0; x < width; x++ {
|
|
si := x * 4
|
|
dx := y
|
|
dy := width - 1 - x
|
|
di := dy*dst.Stride + dx*4
|
|
dst.Pix[di+0] = sRow[si+2]
|
|
dst.Pix[di+1] = sRow[si+1]
|
|
dst.Pix[di+2] = sRow[si+0]
|
|
dst.Pix[di+3] = 255
|
|
}
|
|
}
|
|
return dst
|
|
default:
|
|
dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
for y := 0; y < height; y++ {
|
|
sRow := src[y*pitch : y*pitch+width*4]
|
|
dRow := dst.Pix[y*dst.Stride : y*dst.Stride+width*4]
|
|
for x := 0; x < width; x++ {
|
|
si := x * 4
|
|
di := si
|
|
dRow[di+0] = sRow[si+2]
|
|
dRow[di+1] = sRow[si+1]
|
|
dRow[di+2] = sRow[si+0]
|
|
dRow[di+3] = 255
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
}
|
|
|
|
func drawCursorRotated(img *image.RGBA, cursorBounds, captureBounds image.Rectangle, rotation uint32) {
|
|
if !cursorCaptureEnabled.Load() {
|
|
return
|
|
}
|
|
var ci cursorInfo
|
|
ci.cbSize = uint32(unsafe.Sizeof(ci))
|
|
ret, _, _ := procGetCursorInfo.Call(uintptr(unsafe.Pointer(&ci)))
|
|
if ret == 0 || (ci.flags&CURSOR_SHOWING) == 0 || ci.hCursor == 0 {
|
|
return
|
|
}
|
|
|
|
curX := ci.ptScreenPos.x
|
|
curY := ci.ptScreenPos.y
|
|
|
|
if cursorBounds.Empty() {
|
|
cursorBounds = captureBounds
|
|
}
|
|
|
|
localX := int(curX) - cursorBounds.Min.X
|
|
localY := int(curY) - cursorBounds.Min.Y
|
|
if localX < 0 || localY < 0 || localX >= cursorBounds.Dx() || localY >= cursorBounds.Dy() {
|
|
return
|
|
}
|
|
|
|
scaleX := float64(captureBounds.Dx()) / float64(cursorBounds.Dx())
|
|
scaleY := float64(captureBounds.Dy()) / float64(cursorBounds.Dy())
|
|
localX = int(float64(localX) * scaleX)
|
|
localY = int(float64(localY) * scaleY)
|
|
|
|
w := captureBounds.Dx()
|
|
h := captureBounds.Dy()
|
|
var rx, ry int
|
|
switch rotation {
|
|
case dxgiModeRotationRotate90:
|
|
rx = h - 1 - localY
|
|
ry = localX
|
|
case dxgiModeRotationRotate180:
|
|
rx = w - 1 - localX
|
|
ry = h - 1 - localY
|
|
case dxgiModeRotationRotate270:
|
|
rx = localY
|
|
ry = w - 1 - localX
|
|
default:
|
|
rx = localX
|
|
ry = localY
|
|
}
|
|
rotBounds := image.Rect(0, 0, img.Bounds().Dx(), img.Bounds().Dy())
|
|
|
|
// Performance-first cursor overlay for duplication path: avoid per-frame
|
|
// icon extraction/DC composition and draw a lightweight software cursor.
|
|
drawCursor(img, int32(rx), int32(ry), rotBounds)
|
|
}
|
|
|
|
func drawRealCursorOnImage(img *image.RGBA, hCursor uintptr, hotX, hotY, xHotspot, yHotspot int32) bool {
|
|
if img == nil || hCursor == 0 {
|
|
return false
|
|
}
|
|
w := img.Bounds().Dx()
|
|
h := img.Bounds().Dy()
|
|
if w <= 0 || h <= 0 {
|
|
return false
|
|
}
|
|
|
|
hdcScreen := createDisplayDC()
|
|
fromCreateDC := hdcScreen != 0
|
|
if hdcScreen == 0 {
|
|
hdcScreen = getDC(0)
|
|
if hdcScreen == 0 {
|
|
return false
|
|
}
|
|
}
|
|
defer func() {
|
|
if fromCreateDC {
|
|
deleteDC(hdcScreen)
|
|
} else {
|
|
releaseDC(0, hdcScreen)
|
|
}
|
|
}()
|
|
|
|
hdcMem := createCompatibleDC(hdcScreen)
|
|
if hdcMem == 0 {
|
|
return false
|
|
}
|
|
defer deleteDC(hdcMem)
|
|
|
|
bmi := bitmapInfo{
|
|
bmiHeader: bitmapInfoHeader{
|
|
biSize: uint32(unsafe.Sizeof(bitmapInfoHeader{})),
|
|
biWidth: int32(w),
|
|
biHeight: -int32(h),
|
|
biPlanes: 1,
|
|
biBitCount: 32,
|
|
biCompression: BI_RGB,
|
|
},
|
|
}
|
|
var bits unsafe.Pointer
|
|
hbmp := createDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &bits)
|
|
if hbmp == 0 || bits == nil {
|
|
return false
|
|
}
|
|
defer deleteObject(hbmp)
|
|
|
|
old := selectObject(hdcMem, hbmp)
|
|
if old == 0 {
|
|
return false
|
|
}
|
|
defer selectObject(hdcMem, old)
|
|
|
|
dib := unsafe.Slice((*byte)(bits), len(img.Pix))
|
|
copy(dib, img.Pix)
|
|
|
|
x := hotX - xHotspot
|
|
y := hotY - yHotspot
|
|
r, _, _ := procDrawIconEx.Call(
|
|
hdcMem,
|
|
uintptr(x),
|
|
uintptr(y),
|
|
hCursor,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
uintptr(DI_NORMAL),
|
|
)
|
|
if r == 0 {
|
|
return false
|
|
}
|
|
|
|
copy(img.Pix, dib)
|
|
return true
|
|
}
|