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 }