mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-03 04:29:35 +08:00
130 lines
5.2 KiB
Go
130 lines
5.2 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package windows
|
|
|
|
import (
|
|
"fmt"
|
|
"syscall"
|
|
"unsafe"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc"
|
|
"github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32"
|
|
)
|
|
|
|
func MonitorsEqual(first w32.MONITORINFO, second w32.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 GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) {
|
|
// Adapted from winc.utils.getMonitorInfo TODO: add this to win32
|
|
// See docs for
|
|
//https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
|
|
|
|
var info w32.MONITORINFO
|
|
info.CbSize = uint32(unsafe.Sizeof(info))
|
|
succeeded := w32.GetMonitorInfo(hMonitor, &info)
|
|
if !succeeded {
|
|
return &info, errors.New("Windows call to getMonitorInfo failed")
|
|
}
|
|
return &info, nil
|
|
}
|
|
|
|
func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.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 := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST)
|
|
currentMonInfo, currErr := GetMonitorInfo(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 w32.TRUE
|
|
}
|
|
|
|
monInfo, err := GetMonitorInfo(hMonitor)
|
|
if err != nil {
|
|
screenContainer.errors = append(screenContainer.errors, err)
|
|
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
|
return w32.TRUE
|
|
}
|
|
|
|
width := lprcMonitor.Right - lprcMonitor.Left
|
|
height := lprcMonitor.Bottom - lprcMonitor.Top
|
|
ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1
|
|
ourMonitorData.Height = int(height)
|
|
ourMonitorData.Width = int(width)
|
|
ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo)
|
|
|
|
ourMonitorData.PhysicalSize.Width = int(width)
|
|
ourMonitorData.PhysicalSize.Height = int(height)
|
|
|
|
var dpiX, dpiY uint
|
|
w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY)
|
|
if dpiX == 0 || dpiY == 0 {
|
|
screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen"))
|
|
screenContainer.monitors = append(screenContainer.monitors, Screen{})
|
|
return w32.TRUE
|
|
}
|
|
ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX)
|
|
ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY)
|
|
|
|
// 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 w32.TRUE
|
|
}
|
|
|
|
type ScreenContainer struct {
|
|
monitors []Screen
|
|
errors []error
|
|
mainWinHandle w32.HWND
|
|
}
|
|
|
|
func GetAllScreens(mainWinHandle w32.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)
|
|
errorStrings := []string{}
|
|
|
|
dc := w32.GetDC(0)
|
|
defer w32.ReleaseDC(0, dc)
|
|
succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer))
|
|
if !succeeded {
|
|
return monitorContainer.monitors, errors.New("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))
|
|
}
|
|
}
|
|
|
|
if len(errorStrings) > 0 {
|
|
returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings)
|
|
}
|
|
return monitorContainer.monitors, returnErr
|
|
}
|