mirror of
https://github.com/wailsapp/wails.git
synced 2026-03-14 14:45:49 +01:00
[v3 windows] Support GetScreens and GetPrimaryScreen
This commit is contained in:
parent
798e51d4d3
commit
82ec56bc7d
5 changed files with 179 additions and 111 deletions
|
|
@ -3,7 +3,10 @@
|
|||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/sys/windows"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
|
|
@ -39,13 +42,51 @@ func getNativeApplication() *windowsApp {
|
|||
}
|
||||
|
||||
func (m *windowsApp) getPrimaryScreen() (*Screen, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
screens, err := m.getScreens()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, screen := range screens {
|
||||
if screen.IsPrimary {
|
||||
return screen, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("no primary screen found")
|
||||
}
|
||||
|
||||
func (m *windowsApp) getScreens() ([]*Screen, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
allScreens, err := w32.GetAllScreens()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert result to []*Screen
|
||||
screens := make([]*Screen, len(allScreens))
|
||||
for id, screen := range allScreens {
|
||||
x := int(screen.MONITORINFOEX.RcMonitor.Left)
|
||||
y := int(screen.MONITORINFOEX.RcMonitor.Top)
|
||||
right := int(screen.MONITORINFOEX.RcMonitor.Right)
|
||||
bottom := int(screen.MONITORINFOEX.RcMonitor.Bottom)
|
||||
width := right - x
|
||||
height := bottom - y
|
||||
screens[id] = &Screen{
|
||||
ID: strconv.Itoa(id),
|
||||
Name: windows.UTF16ToString(screen.MONITORINFOEX.SzDevice[:]),
|
||||
X: x,
|
||||
Y: y,
|
||||
Size: Size{Width: width, Height: height},
|
||||
Bounds: Rect{X: x, Y: y, Width: width, Height: height},
|
||||
WorkArea: Rect{
|
||||
X: int(screen.MONITORINFOEX.RcWork.Left),
|
||||
Y: int(screen.MONITORINFOEX.RcWork.Top),
|
||||
Width: int(screen.MONITORINFOEX.RcWork.Right - screen.MONITORINFOEX.RcWork.Left),
|
||||
Height: int(screen.MONITORINFOEX.RcWork.Bottom - screen.MONITORINFOEX.RcWork.Top),
|
||||
},
|
||||
IsPrimary: screen.IsPrimary,
|
||||
Scale: screen.Scale,
|
||||
Rotation: 0,
|
||||
}
|
||||
}
|
||||
return screens, nil
|
||||
}
|
||||
|
||||
func (m *windowsApp) hide() {
|
||||
|
|
|
|||
|
|
@ -493,27 +493,6 @@ func (w *windowsWebviewWindow) getScreen() (*Screen, error) {
|
|||
thisScreen.IsPrimary = mi.DwFlags&w32.MONITORINFOF_PRIMARY != 0
|
||||
|
||||
// TODO: Get screen rotation
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodea
|
||||
|
||||
//// get display settings for monitor
|
||||
//var dm w32.DEVMODE
|
||||
//dm.DmSize = uint16(unsafe.Sizeof(dm))
|
||||
//dm.DmDriverExtra = 0
|
||||
//w32.EnumDisplaySettingsEx(&mi.SzDevice[0], w32.ENUM_CURRENT_SETTINGS, &dm, 0)
|
||||
//
|
||||
//// check display settings for rotation
|
||||
//rotationAngle := dm.DmDi
|
||||
//if rotationAngle == DMDO_0 {
|
||||
// printf("Monitor is not rotated\n")
|
||||
//} else if rotationAngle == DMDO_90 {
|
||||
// printf("Monitor is rotated 90 degrees\n")
|
||||
//} else if rotationAngle == DMDO_180 {
|
||||
// printf("Monitor is rotated 180 degrees\n")
|
||||
//} else if rotationAngle == DMDO_270 {
|
||||
// printf("Monitor is rotated 270 degrees\n")
|
||||
//} else {
|
||||
// printf("Monitor is rotated at an unknown angle\n")
|
||||
//}
|
||||
|
||||
return &thisScreen, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 monitorContainer.monitors, returnErr
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue