github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/windows/screen.go (about) 1 //go:build windows 2 // +build windows 3 4 package windows 5 6 import ( 7 "fmt" 8 "syscall" 9 "unsafe" 10 11 "github.com/pkg/errors" 12 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc" 13 "github.com/secoba/wails/v2/internal/frontend/desktop/windows/winc/w32" 14 ) 15 16 func MonitorsEqual(first w32.MONITORINFO, second w32.MONITORINFO) bool { 17 // Checks to make sure all the fields are the same. 18 // A cleaner way would be to check identity of devices. but I couldn't find a way of doing that using the win32 API 19 return first.DwFlags == second.DwFlags && 20 first.RcMonitor.Top == second.RcMonitor.Top && 21 first.RcMonitor.Bottom == second.RcMonitor.Bottom && 22 first.RcMonitor.Right == second.RcMonitor.Right && 23 first.RcMonitor.Left == second.RcMonitor.Left && 24 first.RcWork.Top == second.RcWork.Top && 25 first.RcWork.Bottom == second.RcWork.Bottom && 26 first.RcWork.Right == second.RcWork.Right && 27 first.RcWork.Left == second.RcWork.Left 28 } 29 30 func GetMonitorInfo(hMonitor w32.HMONITOR) (*w32.MONITORINFO, error) { 31 // Adapted from winc.utils.getMonitorInfo TODO: add this to win32 32 // See docs for 33 //https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa 34 35 var info w32.MONITORINFO 36 info.CbSize = uint32(unsafe.Sizeof(info)) 37 succeeded := w32.GetMonitorInfo(hMonitor, &info) 38 if !succeeded { 39 return &info, errors.New("Windows call to getMonitorInfo failed") 40 } 41 return &info, nil 42 } 43 44 func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, screenContainer *ScreenContainer) uintptr { 45 // adapted from https://stackoverflow.com/a/23492886/4188138 46 47 // see docs for the following pages to better understand this function 48 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors 49 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-monitorenumproc 50 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-monitorinfo 51 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow 52 53 ourMonitorData := Screen{} 54 currentMonHndl := w32.MonitorFromWindow(screenContainer.mainWinHandle, w32.MONITOR_DEFAULTTONEAREST) 55 currentMonInfo, currErr := GetMonitorInfo(currentMonHndl) 56 57 if currErr != nil { 58 screenContainer.errors = append(screenContainer.errors, currErr) 59 screenContainer.monitors = append(screenContainer.monitors, Screen{}) 60 // not sure what the consequences of returning false are, so let's just return true and handle it ourselves 61 return w32.TRUE 62 } 63 64 monInfo, err := GetMonitorInfo(hMonitor) 65 if err != nil { 66 screenContainer.errors = append(screenContainer.errors, err) 67 screenContainer.monitors = append(screenContainer.monitors, Screen{}) 68 return w32.TRUE 69 } 70 71 width := lprcMonitor.Right - lprcMonitor.Left 72 height := lprcMonitor.Bottom - lprcMonitor.Top 73 ourMonitorData.IsPrimary = monInfo.DwFlags&w32.MONITORINFOF_PRIMARY == 1 74 ourMonitorData.Height = int(height) 75 ourMonitorData.Width = int(width) 76 ourMonitorData.IsCurrent = MonitorsEqual(*currentMonInfo, *monInfo) 77 78 ourMonitorData.PhysicalSize.Width = int(width) 79 ourMonitorData.PhysicalSize.Height = int(height) 80 81 var dpiX, dpiY uint 82 w32.GetDPIForMonitor(hMonitor, w32.MDT_EFFECTIVE_DPI, &dpiX, &dpiY) 83 if dpiX == 0 || dpiY == 0 { 84 screenContainer.errors = append(screenContainer.errors, fmt.Errorf("unable to get DPI for screen")) 85 screenContainer.monitors = append(screenContainer.monitors, Screen{}) 86 return w32.TRUE 87 } 88 ourMonitorData.Size.Width = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Width, dpiX) 89 ourMonitorData.Size.Height = winc.ScaleToDefaultDPI(ourMonitorData.PhysicalSize.Height, dpiY) 90 91 // the reason we need a container is that we have don't know how many times this function will be called 92 // this "append" call could potentially do an allocation and rewrite the pointer to monitors. So we save the pointer in screenContainer.monitors 93 // and retrieve the values after all EnumProc calls 94 // If EnumProc is multi-threaded, this could be problematic. Although, I don't think it is. 95 screenContainer.monitors = append(screenContainer.monitors, ourMonitorData) 96 // let's keep screenContainer.errors the same size as screenContainer.monitors in case we want to match them up later if necessary 97 screenContainer.errors = append(screenContainer.errors, nil) 98 return w32.TRUE 99 } 100 101 type ScreenContainer struct { 102 monitors []Screen 103 errors []error 104 mainWinHandle w32.HWND 105 } 106 107 func GetAllScreens(mainWinHandle w32.HWND) ([]Screen, error) { 108 // TODO fix hack of container sharing by having a proper data sharing mechanism between windows and the runtime 109 monitorContainer := ScreenContainer{mainWinHandle: mainWinHandle} 110 returnErr := error(nil) 111 errorStrings := []string{} 112 113 dc := w32.GetDC(0) 114 defer w32.ReleaseDC(0, dc) 115 succeeded := w32.EnumDisplayMonitors(dc, nil, syscall.NewCallback(EnumProc), unsafe.Pointer(&monitorContainer)) 116 if !succeeded { 117 return monitorContainer.monitors, errors.New("Windows call to EnumDisplayMonitors failed") 118 } 119 for idx, err := range monitorContainer.errors { 120 if err != nil { 121 errorStrings = append(errorStrings, fmt.Sprintf("Error from monitor #%v, %v", idx+1, err)) 122 } 123 } 124 125 if len(errorStrings) > 0 { 126 returnErr = fmt.Errorf("%v errors encountered: %v", len(errorStrings), errorStrings) 127 } 128 return monitorContainer.monitors, returnErr 129 }