[v3 windows] Support GetScreens and GetPrimaryScreen

This commit is contained in:
Lea Anthony
2023-06-01 22:52:03 +10:00
parent 798e51d4d3
commit 82ec56bc7d
5 changed files with 190 additions and 122 deletions
+9
View File
@@ -52,6 +52,15 @@ const (
SE_ERR_NOASSOC = 31
)
const (
EDS_ROTATEDMODE = 0x00000001
EDS_RAWMODE = 0x00000002
DMDO_DEFAULT = 0
DMDO_90 = 1
DMDO_180 = 2
DMDO_270 = 3
)
const (
CW_USEDEFAULT = ^0x7fffffff
)
+132 -97
View File
@@ -8,111 +8,146 @@ import (
"unsafe"
)
func MonitorsEqual(first MONITORINFO, second MONITORINFO) bool {
// Checks to make sure all the fields are the same.
// A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API
return first.DwFlags == second.DwFlags &&
first.RcMonitor.Top == second.RcMonitor.Top &&
first.RcMonitor.Bottom == second.RcMonitor.Bottom &&
first.RcMonitor.Right == second.RcMonitor.Right &&
first.RcMonitor.Left == second.RcMonitor.Left &&
first.RcWork.Top == second.RcWork.Top &&
first.RcWork.Bottom == second.RcWork.Bottom &&
first.RcWork.Right == second.RcWork.Right &&
first.RcWork.Left == second.RcWork.Left
}
func GetMonitorInformation(hMonitor HMONITOR) (*MONITORINFO, error) {
// Adapted from winc.utils.getMonitorInfo
// See docs for
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
var info MONITORINFO
info.CbSize = uint32(unsafe.Sizeof(info))
succeeded := GetMonitorInfo(hMonitor, &info)
if !succeeded {
return &info, fmt.Errorf("Windows call to getMonitorInfo failed")
}
return &info, nil
}
type Screen struct {
IsCurrent bool
MONITORINFOEX
Name string
IsPrimary bool
Width int
Height int
IsCurrent bool
Scale float32
Rotation float32
}
func EnumProc(hMonitor HMONITOR, hdcMonitor HDC, lprcMonitor *RECT, screenContainer *ScreenContainer) uintptr {
// adapted from https://stackoverflow.com/a/23492886/4188138
// see docs for the following pages to better understand this function
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
ourMonitorData := Screen{}
currentMonHndl := MonitorFromWindow(screenContainer.mainWinHandle, MONITOR_DEFAULTTONEAREST)
currentMonInfo, currErr := GetMonitorInformation(currentMonHndl)
if currErr != nil {
screenContainer.errors = append(screenContainer.errors, currErr)
screenContainer.monitors = append(screenContainer.monitors, Screen{})
// not sure what the consequences of returning false are, so let's just return true and handle it ourselves
return TRUE
}
monInfo, err := GetMonitorInformation(hMonitor)
if err != nil {
screenContainer.errors = append(screenContainer.errors, err)
screenContainer.monitors = append(screenContainer.monitors, Screen{})
return TRUE
}
height := lprcMonitor.Right - lprcMonitor.Left
width := lprcMonitor.Bottom - lprcMonitor.Top
ourMonitorData.IsPrimary = monInfo.DwFlags&MONITORINFOF_PRIMARY == 1
ourMonitorData.Height = int(width)
ourMonitorData.Width = int(height)
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
// the reason we need a container is that we have don't know how many times this function will be called
// this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors
// and retrieve the values after all EnumProc calls
// If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is.
screenContainer.monitors = append(screenContainer.monitors, ourMonitorData)
// let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary
screenContainer.errors = append(screenContainer.errors, nil)
return TRUE
type DISPLAY_DEVICE struct {
cb uint32
DeviceName [32]uint16
DeviceString [128]uint16
StateFlags uint32
DeviceID [128]uint16
DeviceKey [128]uint16
}
type ScreenContainer struct {
monitors []Screen
errors []error
mainWinHandle HWND
}
func GetAllScreens(mainWinHandle HWND) ([]Screen, error) {
// TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime
monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle}
returnErr := error(nil)
var errorStrings []string
dc := GetDC(0)
defer ReleaseDC(0, dc)
succeeded := EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
if !succeeded {
return monitorContainer.monitors, fmt.Errorf("Windows call to EnumDisplayMonitors failed")
}
for idx, err := range monitorContainer.errors {
if err != nil {
errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err))
func getMonitorName(deviceName string) (string, error) {
var device DISPLAY_DEVICE
device.cb = uint32(unsafe.Sizeof(device))
i := uint32(0)
for {
res, _, _ := procEnumDisplayDevices.Call(uintptr(unsafe.Pointer(MustStringToUTF16Ptr(deviceName))), uintptr(i), uintptr(unsafe.Pointer(&device)), 0)
if res == 0 {
break
}
if device.StateFlags&0x1 != 0 {
return syscall.UTF16ToString(device.DeviceString[:]), nil
}
}
if len(errorStrings) > 0 {
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
return "", fmt.Errorf("monitor name not found for device: %s", deviceName)
}
// I'm not convinced this works properly
func GetRotationForMonitor(displayName [32]uint16) (float32, error) {
var devMode DEVMODE
devMode.DmSize = uint16(unsafe.Sizeof(devMode))
resp, _, _ := procEnumDisplaySettings.Call(uintptr(unsafe.Pointer(&displayName[0])), ENUM_CURRENT_SETTINGS, uintptr(unsafe.Pointer(&devMode)))
if resp == 0 {
return 0, fmt.Errorf("EnumDisplaySettings failed")
}
return monitorContainer.monitors, returnErr
if (devMode.DmFields & DM_DISPLAYORIENTATION) == 0 {
return 0, fmt.Errorf("DM_DISPLAYORIENTATION not set")
}
switch devMode.DmOrientation {
case DMDO_DEFAULT:
return 0, nil
case DMDO_90:
return 90, nil
case DMDO_180:
return 180, nil
case DMDO_270:
return 270, nil
}
return -1, nil
}
func GetAllScreens() ([]*Screen, error) {
var monitorList []MONITORINFOEX
enumFunc := func(hMonitor uintptr, hdc uintptr, lprcMonitor *RECT, lParam uintptr) uintptr {
monitor := MONITORINFOEX{
MONITORINFO: MONITORINFO{
CbSize: uint32(unsafe.Sizeof(MONITORINFOEX{})),
},
SzDevice: [32]uint16{},
}
ret, _, _ := procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitor)))
if ret == 0 {
return 1 // Continue enumeration
}
monitorList = append(monitorList, monitor)
return 1 // Continue enumeration
}
ret, _, _ := procEnumDisplayMonitors.Call(0, 0, syscall.NewCallback(enumFunc), 0)
if ret == 0 {
return nil, fmt.Errorf("EnumDisplayMonitors failed")
}
// Get the active screen
var pt POINT
ret, _, _ = procGetCursorPos.Call(uintptr(unsafe.Pointer(&pt)))
if ret == 0 {
return nil, fmt.Errorf("GetCursorPos failed")
}
hMonitor, _, _ := procMonitorFromPoint.Call(uintptr(unsafe.Pointer(&pt)), MONITOR_DEFAULTTONEAREST)
if hMonitor == 0 {
return nil, fmt.Errorf("MonitorFromPoint failed")
}
var monitorInfo MONITORINFO
monitorInfo.CbSize = uint32(unsafe.Sizeof(monitorInfo))
ret, _, _ = procGetMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&monitorInfo)))
if ret == 0 {
return nil, fmt.Errorf("GetMonitorInfo failed")
}
var result []*Screen
// Iterate through the screens and set the active one
for _, monitor := range monitorList {
thisContainer := &Screen{
MONITORINFOEX: monitor,
}
thisContainer.IsCurrent = equalRect(monitor.RcMonitor, monitorInfo.RcMonitor)
thisContainer.IsPrimary = monitor.DwFlags == MONITORINFOF_PRIMARY
name, err := getMonitorName(syscall.UTF16ToString(monitor.SzDevice[:]))
if err != nil {
name = ""
}
// Get DPI for monitor
var dpiX, dpiY uint
ret = GetDPIForMonitor(hMonitor, MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
if ret != S_OK {
return nil, fmt.Errorf("GetDpiForMonitor failed")
}
// Convert to float32
thisContainer.Scale = float32(dpiX) / 96.0
// Get rotation of monitor
rot, err := GetRotationForMonitor(monitor.SzDevice)
if err != nil {
rot = 0
}
thisContainer.Rotation = rot
thisContainer.Name = name
result = append(result, thisContainer)
}
return result, nil
}
func equalRect(a RECT, b RECT) bool {
return a.Left == b.Left && a.Top == b.Top && a.Right == b.Right && a.Bottom == b.Bottom
}
+4
View File
@@ -131,6 +131,8 @@ var (
procGetDpiForWindow = moduser32.NewProc("GetDpiForWindow")
procSetProcessDPIAware = moduser32.NewProc("SetProcessDPIAware")
procEnumDisplayMonitors = moduser32.NewProc("EnumDisplayMonitors")
procEnumDisplayDevices = moduser32.NewProc("EnumDisplayDevicesW")
procEnumDisplaySettings = moduser32.NewProc("EnumDisplaySettingsW")
procEnumDisplaySettingsEx = moduser32.NewProc("EnumDisplaySettingsExW")
procChangeDisplaySettingsEx = moduser32.NewProc("ChangeDisplaySettingsExW")
procSendInput = moduser32.NewProc("SendInput")
@@ -139,6 +141,8 @@ var (
procCallNextHookEx = moduser32.NewProc("CallNextHookEx")
procGetForegroundWindow = moduser32.NewProc("GetForegroundWindow")
procUpdateLayeredWindow = moduser32.NewProc("UpdateLayeredWindow")
getDisplayConfig = moduser32.NewProc("GetDisplayConfigBufferSizes")
queryDisplayConfig = moduser32.NewProc("QueryDisplayConfig")
procSystemParametersInfo = moduser32.NewProc("SystemParametersInfoW")
procSetClassLong = moduser32.NewProc("SetClassLongW")