go-tg-screenshot-bot
213 строк · 6.0 Кб
1//go:build windows
2
3package screenshot4
5import (6"errors"7"github.com/lxn/win"8"image"9"syscall"10"unsafe"11)
12
13var (14libUser32, _ = syscall.LoadLibrary("user32.dll")15funcGetDesktopWindow, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "GetDesktopWindow")16funcEnumDisplayMonitors, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "EnumDisplayMonitors")17funcGetMonitorInfo, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "GetMonitorInfoW")18funcEnumDisplaySettings, _ = syscall.GetProcAddress(syscall.Handle(libUser32), "EnumDisplaySettingsW")19)
20
21func Capture(x, y, width, height int) (*image.RGBA, error) {22rect := image.Rect(0, 0, width, height)23img, err := createImage(rect)24if err != nil {25return nil, err26}27
28hwnd := getDesktopWindow()29hdc := win.GetDC(hwnd)30if hdc == 0 {31return nil, errors.New("GetDC failed")32}33defer win.ReleaseDC(hwnd, hdc)34
35memory_device := win.CreateCompatibleDC(hdc)36if memory_device == 0 {37return nil, errors.New("CreateCompatibleDC failed")38}39defer win.DeleteDC(memory_device)40
41bitmap := win.CreateCompatibleBitmap(hdc, int32(width), int32(height))42if bitmap == 0 {43return nil, errors.New("CreateCompatibleBitmap failed")44}45defer win.DeleteObject(win.HGDIOBJ(bitmap))46
47var header win.BITMAPINFOHEADER48header.BiSize = uint32(unsafe.Sizeof(header))49header.BiPlanes = 150header.BiBitCount = 3251header.BiWidth = int32(width)52header.BiHeight = int32(-height)53header.BiCompression = win.BI_RGB54header.BiSizeImage = 055
56// GetDIBits balks at using Go memory on some systems. The MSDN example uses57// GlobalAlloc, so we'll do that too. See:58// https://docs.microsoft.com/en-gb/windows/desktop/gdi/capturing-an-image59bitmapDataSize := uintptr(((int64(width)*int64(header.BiBitCount) + 31) / 32) * 4 * int64(height))60hmem := win.GlobalAlloc(win.GMEM_MOVEABLE, bitmapDataSize)61defer win.GlobalFree(hmem)62memptr := win.GlobalLock(hmem)63defer win.GlobalUnlock(hmem)64
65old := win.SelectObject(memory_device, win.HGDIOBJ(bitmap))66if old == 0 {67return nil, errors.New("SelectObject failed")68}69defer win.SelectObject(memory_device, old)70
71if !win.BitBlt(memory_device, 0, 0, int32(width), int32(height), hdc, int32(x), int32(y), win.SRCCOPY) {72return nil, errors.New("BitBlt failed")73}74
75if win.GetDIBits(hdc, bitmap, 0, uint32(height), (*uint8)(memptr), (*win.BITMAPINFO)(unsafe.Pointer(&header)), win.DIB_RGB_COLORS) == 0 {76return nil, errors.New("GetDIBits failed")77}78
79i := 080src := uintptr(memptr)81for y := 0; y < height; y++ {82for x := 0; x < width; x++ {83v0 := *(*uint8)(unsafe.Pointer(src))84v1 := *(*uint8)(unsafe.Pointer(src + 1))85v2 := *(*uint8)(unsafe.Pointer(src + 2))86
87// BGRA => RGBA, and set A to 25588img.Pix[i], img.Pix[i+1], img.Pix[i+2], img.Pix[i+3] = v2, v1, v0, 25589
90i += 491src += 492}93}94
95return img, nil96}
97
98func NumActiveDisplays() int {99var count int = 0100enumDisplayMonitors(win.HDC(0), nil, syscall.NewCallback(countupMonitorCallback), uintptr(unsafe.Pointer(&count)))101return count102}
103
104func GetDisplayBounds(displayIndex int) image.Rectangle {105var ctx getMonitorBoundsContext106ctx.Index = displayIndex107ctx.Count = 0108enumDisplayMonitors(win.HDC(0), nil, syscall.NewCallback(getMonitorBoundsCallback), uintptr(unsafe.Pointer(&ctx)))109return image.Rect(110int(ctx.Rect.Left), int(ctx.Rect.Top),111int(ctx.Rect.Right), int(ctx.Rect.Bottom))112}
113
114func getDesktopWindow() win.HWND {115ret, _, _ := syscall.Syscall(funcGetDesktopWindow, 0, 0, 0, 0)116return win.HWND(ret)117}
118
119func enumDisplayMonitors(hdc win.HDC, lprcClip *win.RECT, lpfnEnum uintptr, dwData uintptr) bool {120ret, _, _ := syscall.Syscall6(funcEnumDisplayMonitors, 4,121uintptr(hdc),122uintptr(unsafe.Pointer(lprcClip)),123lpfnEnum,124dwData,1250,1260)127return int(ret) != 0128}
129
130func countupMonitorCallback(hMonitor win.HMONITOR, hdcMonitor win.HDC, lprcMonitor *win.RECT, dwData uintptr) uintptr {131var count *int132count = (*int)(unsafe.Pointer(dwData))133*count = *count + 1134return uintptr(1)135}
136
137type getMonitorBoundsContext struct {138Index int139Rect win.RECT140Count int141}
142
143func getMonitorBoundsCallback(hMonitor win.HMONITOR, hdcMonitor win.HDC, lprcMonitor *win.RECT, dwData uintptr) uintptr {144var ctx *getMonitorBoundsContext145ctx = (*getMonitorBoundsContext)(unsafe.Pointer(dwData))146if ctx.Count != ctx.Index {147ctx.Count = ctx.Count + 1148return uintptr(1)149}150
151if realSize := getMonitorRealSize(hMonitor); realSize != nil {152ctx.Rect = *realSize153} else {154ctx.Rect = *lprcMonitor155}156
157return uintptr(0)158}
159
160type _MONITORINFOEX struct {161win.MONITORINFO162DeviceName [win.CCHDEVICENAME]uint16163}
164
165const _ENUM_CURRENT_SETTINGS = 0xFFFFFFFF166
167type _DEVMODE struct {168_ [68]byte169DmSize uint16170_ [6]byte171DmPosition win.POINT172_ [86]byte173DmPelsWidth uint32174DmPelsHeight uint32175_ [40]byte176}
177
178// getMonitorRealSize makes a call to GetMonitorInfo
179// to obtain the device name for the monitor handle
180// provided to the method.
181//
182// With the device name, EnumDisplaySettings is called to
183// obtain the current configuration for the monitor, this
184// information includes the real resolution of the monitor
185// rather than the scaled version based on DPI.
186//
187// If either handle calls fail, it will return a nil
188// allowing the calling method to use the bounds information
189// returned by EnumDisplayMonitors which may be affected
190// by DPI.
191func getMonitorRealSize(hMonitor win.HMONITOR) *win.RECT {192info := _MONITORINFOEX{}193info.CbSize = uint32(unsafe.Sizeof(info))194
195ret, _, _ := syscall.Syscall(funcGetMonitorInfo, 2, uintptr(hMonitor), uintptr(unsafe.Pointer(&info)), 0)196if ret == 0 {197return nil198}199
200devMode := _DEVMODE{}201devMode.DmSize = uint16(unsafe.Sizeof(devMode))202
203if ret, _, _ := syscall.Syscall(funcEnumDisplaySettings, 3, uintptr(unsafe.Pointer(&info.DeviceName[0])), _ENUM_CURRENT_SETTINGS, uintptr(unsafe.Pointer(&devMode))); ret == 0 {204return nil205}206
207return &win.RECT{208Left: devMode.DmPosition.X,209Right: devMode.DmPosition.X + int32(devMode.DmPelsWidth),210Top: devMode.DmPosition.Y,211Bottom: devMode.DmPosition.Y + int32(devMode.DmPelsHeight),212}213}
214