github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/screen.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // Copyright (C) 2020 - 2023 iDigitalFlame
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18  //
    19  
    20  package winapi
    21  
    22  import (
    23  	"image"
    24  	"image/color"
    25  	"image/png"
    26  	"io"
    27  	"runtime"
    28  	"runtime/debug"
    29  	"sync"
    30  	"syscall"
    31  	"unsafe"
    32  )
    33  
    34  var screenFunctions struct {
    35  	_ [0]func()
    36  	sync.Once
    37  	c, b uintptr
    38  }
    39  
    40  type rect struct {
    41  	Left   int32
    42  	Top    int32
    43  	Right  int32
    44  	Bottom int32
    45  }
    46  type point struct {
    47  	X, Y int32
    48  }
    49  type rgbQuad struct {
    50  	Blue  byte
    51  	Green byte
    52  	Red   byte
    53  	_     byte
    54  }
    55  type devMode struct {
    56  	_        [68]byte
    57  	Size     uint16
    58  	_        [6]byte
    59  	Position point
    60  	_        [86]byte
    61  	Width    uint32
    62  	Height   uint32
    63  	_        [40]byte
    64  }
    65  type imagePtr struct {
    66  	_ [0]func()
    67  	h uintptr
    68  	b image.Rectangle
    69  }
    70  type boundsInfo struct {
    71  	Index uint32
    72  	Rect  rect
    73  	Count uint32
    74  }
    75  type bitmapInfo struct {
    76  	Header bitmapInfoHeader
    77  	Colors *rgbQuad
    78  }
    79  type monitorInfo struct {
    80  	Size    uint32
    81  	Monitor rect
    82  	Work    rect
    83  	Flags   uint32
    84  }
    85  type monitorInfoEx struct {
    86  	monitorInfo
    87  	Name [32]uint16
    88  }
    89  type bitmapInfoHeader struct {
    90  	Size          uint32
    91  	Width         int32
    92  	Height        int32
    93  	Planes        uint16
    94  	BitCount      uint16
    95  	Compression   uint32
    96  	SizeImage     uint32
    97  	XPelsPerMeter int32
    98  	YPelsPerMeter int32
    99  	ClrUsed       uint32
   100  	ClrImportant  uint32
   101  }
   102  
   103  func initCallbacks() {
   104  	screenFunctions.c = syscall.NewCallback(monitorCountCallback)
   105  	screenFunctions.b = syscall.NewCallback(monitorBoundsCallback)
   106  }
   107  func releaseDC(w, h uintptr) error {
   108  	r, _, err := syscallN(funcReleaseDC.address(), w, h)
   109  	if r == 0 {
   110  		return unboxError(err)
   111  	}
   112  	return nil
   113  }
   114  
   115  // ActiveDisplays returns the count of current active displays enabled on the
   116  // device.
   117  //
   118  // This function returns an error if any error occurs when retrieving the display
   119  // count.
   120  func ActiveDisplays() (uint32, error) {
   121  	screenFunctions.Do(initCallbacks)
   122  	var (
   123  		c   uint32
   124  		err = enumDisplayMonitors(0, nil, screenFunctions.c, uintptr(unsafe.Pointer(&c)))
   125  	)
   126  	return c, err
   127  }
   128  func getDC(w uintptr) (uintptr, error) {
   129  	r, _, err := syscallN(funcGetDC.address(), w)
   130  	if r == 0 {
   131  		return 0, unboxError(err)
   132  	}
   133  	return r, nil
   134  }
   135  func (imagePtr) ColorModel() color.Model {
   136  	return color.RGBAModel
   137  }
   138  func getDesktopWindow() (uintptr, error) {
   139  	r, _, err := syscallN(funcGetDesktopWindow.address())
   140  	if r == 0 {
   141  		return 0, unboxError(err)
   142  	}
   143  	return r, nil
   144  }
   145  func getMonitorRealSize(h uintptr) *rect {
   146  	var i monitorInfoEx
   147  	i.Size = 104
   148  	if err := getMonitorInfo(h, &i); err != nil {
   149  		return nil
   150  	}
   151  	d := devMode{Size: 220}
   152  	if err := enumDisplaySettings(i.Name, true, &d); err != nil {
   153  		return nil
   154  	}
   155  	return &rect{
   156  		Left:   d.Position.X,
   157  		Right:  d.Position.X + int32(d.Width),
   158  		Top:    d.Position.Y,
   159  		Bottom: d.Position.Y + int32(d.Height),
   160  	}
   161  }
   162  func deleteDC(h uintptr) (uintptr, error) {
   163  	r, _, err := syscallN(funcDeleteDC.address(), h)
   164  	if r == 0 {
   165  		return 0, unboxError(err)
   166  	}
   167  	return r, nil
   168  }
   169  func (i imagePtr) At(x, y int) color.Color {
   170  	// NOTE(dij): There's no point of double copy-ing the bytes, so we just use
   171  	//            this helper to "flip" it (it's basically reversed with alpha
   172  	//            as a constant) and write it out to the Writer directly.
   173  	//
   174  	//            Gotta do this as the runtime seems to like to hold onto the
   175  	//            RGBA struct for some reason, even when we clear it.
   176  	if i.b.Min.X > x || x >= i.b.Max.X || i.b.Min.Y > y || y >= i.b.Max.Y {
   177  		return color.RGBA64{}
   178  	}
   179  	b := *(*[4]byte)(unsafe.Pointer(i.h + uintptr((y-i.b.Min.Y)*(4*(i.b.Max.X-i.b.Min.X))+(x-i.b.Min.X)*4)))
   180  	return color.RGBA64{
   181  		R: uint16(b[2])<<8 | uint16(b[2]),
   182  		G: uint16(b[1])<<8 | uint16(b[1]),
   183  		B: uint16(b[0])<<8 | uint16(b[0]),
   184  		A: 0xFFFF,
   185  	}
   186  }
   187  func (i imagePtr) Bounds() image.Rectangle {
   188  	return i.b
   189  }
   190  func deleteObject(h uintptr) (uintptr, error) {
   191  	r, _, err := syscallN(funcDeleteObject.address(), h)
   192  	if r == 0 {
   193  		return 0, unboxError(err)
   194  	}
   195  	return r, nil
   196  }
   197  func selectObject(h, sel uintptr) (uintptr, error) {
   198  	r, _, err := syscallN(funcSelectObject.address(), h, sel)
   199  	if r == 0 {
   200  		return 0, unboxError(err)
   201  	}
   202  	return r, nil
   203  }
   204  func createCompatibleDC(m uintptr) (uintptr, error) {
   205  	r, _, err := syscallN(funcCreateCompatibleDC.address(), m)
   206  	if r == 0 {
   207  		return 0, unboxError(err)
   208  	}
   209  	return r, nil
   210  }
   211  
   212  // DisplayBounds returns the bounds of the supplied display index.
   213  //
   214  // This function will return the bounds of the first monitor if the index is out
   215  // of bounds of the current display count.
   216  func DisplayBounds(i uint32) (image.Rectangle, error) {
   217  	screenFunctions.Do(initCallbacks)
   218  	v := boundsInfo{Index: i}
   219  	enumDisplayMonitors(0, nil, screenFunctions.b, uintptr(unsafe.Pointer(&v)))
   220  	return image.Rect(int(v.Rect.Left), int(v.Rect.Top), int(v.Rect.Right), int(v.Rect.Bottom)), nil
   221  }
   222  func getMonitorInfo(h uintptr, m *monitorInfoEx) error {
   223  	r, _, err := syscallN(funcGetMonitorInfo.address(), h, uintptr(unsafe.Pointer(m)))
   224  	if r == 0 {
   225  		return unboxError(err)
   226  	}
   227  	return nil
   228  }
   229  
   230  // ScreenShot attempts to take a PNG-encoded screenshot of the current dimensions
   231  // specified into the supplied io.Writer.
   232  //
   233  // This function will return an error if any of the API calls or encoding the
   234  // image fails.
   235  func ScreenShot(x, y, width, height uint32, w io.Writer) error {
   236  	p, err := heapCreate(uint64(((int64(width)*32 + 31) / 32) * 4 * int64(height)))
   237  	if err != nil {
   238  		return err
   239  	}
   240  	v, err := getDesktopWindow()
   241  	if err != nil {
   242  		return err
   243  	}
   244  	m, err := getDC(v)
   245  	if err != nil {
   246  		return err
   247  	}
   248  	d, err := createCompatibleDC(m)
   249  	if err != nil {
   250  		releaseDC(v, m)
   251  		return err
   252  	}
   253  	var b uintptr
   254  	if b, err = createCompatibleBitmap(m, width, height); err == nil {
   255  		var (
   256  			h = bitmapInfoHeader{
   257  				Size:        40,
   258  				Width:       int32(width),
   259  				Planes:      1,
   260  				Height:      -int32(height),
   261  				BitCount:    32,
   262  				SizeImage:   0,
   263  				Compression: 0,
   264  			}
   265  			l, o uintptr
   266  		)
   267  		if l, err = heapAlloc(p, uint64(((int64(width)*32+31)/32)*4*int64(height)), false); err == nil {
   268  			if o, err = selectObject(d, b); err == nil {
   269  				if err = bitBlt(d, 0, 0, width, height, m, x, y, 0xCC0020); err == nil {
   270  					if _, err = getDIBits(m, b, 0, height, (*uint8)(unsafe.Pointer(l)), (*bitmapInfo)(unsafe.Pointer(&h)), 0); err == nil {
   271  						err = png.Encode(w, imagePtr{h: l, b: image.Rect(0, 0, int(width), int(height))})
   272  					}
   273  				}
   274  				selectObject(d, o)
   275  			}
   276  			heapFree(p, l)
   277  		}
   278  		deleteObject(b)
   279  	}
   280  	deleteDC(d)
   281  	releaseDC(v, m)
   282  	heapDestroy(p)
   283  	runtime.GC()
   284  	debug.FreeOSMemory()
   285  	return err
   286  }
   287  func monitorCountCallback(_, _ uintptr, _ *rect, d uintptr) uintptr {
   288  	n := (*uint32)(unsafe.Pointer(d))
   289  	*n = *n + 1
   290  	return 1
   291  }
   292  func monitorBoundsCallback(h, _ uintptr, p *rect, d uintptr) uintptr {
   293  	v := (*boundsInfo)(unsafe.Pointer(d))
   294  	if v.Count != v.Index {
   295  		v.Count = v.Count + 1
   296  		return 1
   297  	}
   298  	if r := getMonitorRealSize(h); r != nil {
   299  		v.Rect = *r
   300  	} else {
   301  		v.Rect = *p
   302  	}
   303  	return 0
   304  }
   305  func createCompatibleBitmap(m uintptr, x, y uint32) (uintptr, error) {
   306  	r, _, err := syscallN(funcCreateCompatibleBitmap.address(), m, uintptr(x), uintptr(y))
   307  	if r == 0 {
   308  		return 0, unboxError(err)
   309  	}
   310  	return r, nil
   311  }
   312  func enumDisplaySettings(n [32]uint16, current bool, d *devMode) error {
   313  	var m uint32
   314  	if current {
   315  		m = 0xFFFFFFFF
   316  	}
   317  	r, _, err := syscallN(
   318  		funcEnumDisplaySettings.address(), uintptr(unsafe.Pointer(&n[0])), uintptr(m), uintptr(unsafe.Pointer(d)),
   319  	)
   320  	if r == 0 {
   321  		return unboxError(err)
   322  	}
   323  	return nil
   324  }
   325  func enumDisplayMonitors(h uintptr, p *rect, f uintptr, d uintptr) error {
   326  	r, _, err := syscallN(funcEnumDisplayMonitors.address(), h, uintptr(unsafe.Pointer(p)), f, d)
   327  	if r == 0 {
   328  		return unboxError(err)
   329  	}
   330  	return nil
   331  }
   332  func bitBlt(h uintptr, x, y, w, g uint32, s uintptr, x1, y1, f uint32) error {
   333  	r, _, err := syscallN(
   334  		funcBitBlt.address(), h, uintptr(x), uintptr(y), uintptr(w), uintptr(g), s, uintptr(x1), uintptr(y1), uintptr(f),
   335  	)
   336  	if r == 0 {
   337  		return unboxError(err)
   338  	}
   339  	return nil
   340  }
   341  func getDIBits(h, b uintptr, s, l uint32, m *uint8, i *bitmapInfo, f uint32) (uint32, error) {
   342  	r, _, err := syscallN(
   343  		funcGetDIBits.address(), h, b, uintptr(s), uintptr(l), uintptr(unsafe.Pointer(m)), uintptr(unsafe.Pointer(i)),
   344  		uintptr(f),
   345  	)
   346  	if r == 0 {
   347  		return 0, unboxError(err)
   348  	}
   349  	return uint32(r), nil
   350  }