inital commit
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
//go:build windows
|
||||
|
||||
package capture
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
procEnumDisplayMonitors = user32.NewProc("EnumDisplayMonitors")
|
||||
procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW")
|
||||
procEnumDisplaySettingsW = user32.NewProc("EnumDisplaySettingsW")
|
||||
)
|
||||
|
||||
type rect struct {
|
||||
left int32
|
||||
top int32
|
||||
right int32
|
||||
bottom int32
|
||||
}
|
||||
|
||||
type monitorInfoEx struct {
|
||||
cbSize uint32
|
||||
rcMonitor rect
|
||||
rcWork rect
|
||||
dwFlags uint32
|
||||
szDevice [32]uint16
|
||||
}
|
||||
|
||||
type devMode struct {
|
||||
dmDeviceName [32]uint16
|
||||
dmSpecVersion uint16
|
||||
dmDriverVersion uint16
|
||||
dmSize uint16
|
||||
dmDriverExtra uint16
|
||||
dmFields uint32
|
||||
dmPositionX int32
|
||||
dmPositionY int32
|
||||
dmDisplayOrientation uint32
|
||||
dmDisplayFixedOutput uint32
|
||||
dmColor int16
|
||||
dmDuplex int16
|
||||
dmYResolution int16
|
||||
dmTTOption int16
|
||||
dmCollate int16
|
||||
dmFormName [32]uint16
|
||||
dmLogPixels uint16
|
||||
dmBitsPerPel uint32
|
||||
dmPelsWidth uint32
|
||||
dmPelsHeight uint32
|
||||
dmDisplayFlags uint32
|
||||
dmDisplayFrequency uint32
|
||||
dmICMMethod uint32
|
||||
dmICMIntent uint32
|
||||
dmMediaType uint32
|
||||
dmDitherType uint32
|
||||
dmReserved1 uint32
|
||||
dmReserved2 uint32
|
||||
dmPanningWidth uint32
|
||||
dmPanningHeight uint32
|
||||
}
|
||||
|
||||
const (
|
||||
monitorInfofPrimary = 1
|
||||
enumCurrentSettings = 0xFFFFFFFF
|
||||
)
|
||||
|
||||
type monitorDesc struct {
|
||||
name string
|
||||
rect image.Rectangle
|
||||
workRect image.Rectangle
|
||||
primary bool
|
||||
physW int
|
||||
physH int
|
||||
scale float64
|
||||
posX int
|
||||
posY int
|
||||
}
|
||||
|
||||
var (
|
||||
monitorsCache atomic.Value
|
||||
monitorsMu sync.Mutex
|
||||
monitorsTs int64
|
||||
)
|
||||
|
||||
func displayCount() int {
|
||||
setDPIAware()
|
||||
return len(monitorList())
|
||||
}
|
||||
|
||||
func displayBounds(idx int) image.Rectangle {
|
||||
mons := monitorList()
|
||||
if idx >= 0 && idx < len(mons) {
|
||||
return mons[idx].rect
|
||||
}
|
||||
|
||||
if len(mons) > 0 {
|
||||
return mons[0].rect
|
||||
}
|
||||
|
||||
w := int(getSystemMetric(SM_CXSCREEN))
|
||||
h := int(getSystemMetric(SM_CYSCREEN))
|
||||
return image.Rect(0, 0, w, h)
|
||||
}
|
||||
|
||||
func displayScale(idx int) float64 {
|
||||
mons := monitorList()
|
||||
if idx >= 0 && idx < len(mons) {
|
||||
if mons[idx].scale > 0 {
|
||||
return mons[idx].scale
|
||||
}
|
||||
}
|
||||
return 1.0
|
||||
}
|
||||
|
||||
func monitorList() []monitorDesc {
|
||||
setDPIAware()
|
||||
if v, ok := monitorsCache.Load().([]monitorDesc); ok && len(v) > 0 && time.Since(time.Unix(0, monitorsTs)) < 30*time.Second {
|
||||
return v
|
||||
}
|
||||
|
||||
monitorsMu.Lock()
|
||||
defer monitorsMu.Unlock()
|
||||
|
||||
if v, ok := monitorsCache.Load().([]monitorDesc); ok && len(v) > 0 && time.Since(time.Unix(0, monitorsTs)) < 30*time.Second {
|
||||
return v
|
||||
}
|
||||
|
||||
mons := enumerateMonitors()
|
||||
monitorsCache.Store(mons)
|
||||
monitorsTs = time.Now().UnixNano()
|
||||
logMonitors(mons)
|
||||
return mons
|
||||
}
|
||||
|
||||
func ResetMonitorCache() {
|
||||
monitorsMu.Lock()
|
||||
defer monitorsMu.Unlock()
|
||||
monitorsCache.Store([]monitorDesc{})
|
||||
monitorsTs = 0
|
||||
}
|
||||
|
||||
func enumerateMonitors() []monitorDesc {
|
||||
var result []monitorDesc
|
||||
|
||||
cb := syscall.NewCallback(func(hMonitor, hdc, lprcMonitor, lparam uintptr) uintptr {
|
||||
var mi monitorInfoEx
|
||||
mi.cbSize = uint32(unsafe.Sizeof(mi))
|
||||
if ret, _, _ := procGetMonitorInfoW.Call(hMonitor, uintptr(unsafe.Pointer(&mi))); ret == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
name := windows.UTF16ToString(mi.szDevice[:])
|
||||
primary := mi.dwFlags&monitorInfofPrimary != 0
|
||||
|
||||
var dm devMode
|
||||
dm.dmSize = uint16(unsafe.Sizeof(dm))
|
||||
var physW, physH int
|
||||
var scale float64 = 1.0
|
||||
var posX, posY int
|
||||
if ret, _, _ := procEnumDisplaySettingsW.Call(uintptr(unsafe.Pointer(&mi.szDevice[0])), enumCurrentSettings, uintptr(unsafe.Pointer(&dm))); ret != 0 {
|
||||
physW = int(dm.dmPelsWidth)
|
||||
physH = int(dm.dmPelsHeight)
|
||||
posX = int(dm.dmPositionX)
|
||||
posY = int(dm.dmPositionY)
|
||||
vw := int(mi.rcMonitor.right - mi.rcMonitor.left)
|
||||
if vw > 0 {
|
||||
scale = float64(dm.dmPelsWidth) / float64(vw)
|
||||
}
|
||||
}
|
||||
|
||||
r := image.Rect(int(mi.rcMonitor.left), int(mi.rcMonitor.top), int(mi.rcMonitor.right), int(mi.rcMonitor.bottom))
|
||||
w := image.Rect(int(mi.rcWork.left), int(mi.rcWork.top), int(mi.rcWork.right), int(mi.rcWork.bottom))
|
||||
|
||||
result = append(result, monitorDesc{
|
||||
name: name,
|
||||
rect: r,
|
||||
workRect: w,
|
||||
primary: primary,
|
||||
physW: physW,
|
||||
physH: physH,
|
||||
scale: scale,
|
||||
posX: posX,
|
||||
posY: posY,
|
||||
})
|
||||
return 1
|
||||
})
|
||||
|
||||
if ret, _, _ := procEnumDisplayMonitors.Call(0, 0, cb, 0); ret == 0 {
|
||||
|
||||
w := int(getSystemMetric(SM_CXSCREEN))
|
||||
h := int(getSystemMetric(SM_CYSCREEN))
|
||||
return []monitorDesc{{
|
||||
name: "Primary",
|
||||
rect: image.Rect(0, 0, w, h),
|
||||
primary: true,
|
||||
physW: w,
|
||||
physH: h,
|
||||
scale: 1.0,
|
||||
}}
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
if result[i].primary != result[j].primary {
|
||||
return result[i].primary
|
||||
}
|
||||
oi := deviceOrdinal(result[i].name)
|
||||
oj := deviceOrdinal(result[j].name)
|
||||
if oi >= 0 && oj >= 0 && oi != oj {
|
||||
return oi < oj
|
||||
}
|
||||
if result[i].rect.Min.Y == result[j].rect.Min.Y {
|
||||
return result[i].rect.Min.X < result[j].rect.Min.X
|
||||
}
|
||||
return result[i].rect.Min.Y < result[j].rect.Min.Y
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func deviceOrdinal(name string) int {
|
||||
name = strings.TrimSpace(name)
|
||||
end := len(name)
|
||||
start := end
|
||||
for start > 0 {
|
||||
c := name[start-1]
|
||||
if c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
start--
|
||||
}
|
||||
if start == end {
|
||||
return -1
|
||||
}
|
||||
n, err := strconv.Atoi(name[start:end])
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func logMonitors(mons []monitorDesc) {
|
||||
log.Printf("capture: detected %d monitor(s)", len(mons))
|
||||
for i, m := range mons {
|
||||
log.Printf("capture: monitor %d name=%q primary=%v virtual=%v phys=%dx%d scale=%.2f pos=(%d,%d)", i, m.name, m.primary, m.rect, m.physW, m.physH, m.scale, m.posX, m.posY)
|
||||
}
|
||||
}
|
||||
|
||||
func MonitorInfos() []MonitorInfo {
|
||||
mons := monitorList()
|
||||
if len(mons) == 0 {
|
||||
return nil
|
||||
}
|
||||
infos := make([]MonitorInfo, 0, len(mons))
|
||||
for _, m := range mons {
|
||||
w := m.rect.Dx()
|
||||
h := m.rect.Dy()
|
||||
if w < 0 {
|
||||
w = 0
|
||||
}
|
||||
if h < 0 {
|
||||
h = 0
|
||||
}
|
||||
infos = append(infos, MonitorInfo{Width: w, Height: h})
|
||||
}
|
||||
return infos
|
||||
}
|
||||
Reference in New Issue
Block a user