impl_windows.go (5792B)
1 // Copyright 2018 The Ebiten Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package devicescale 16 17 import ( 18 "fmt" 19 "runtime" 20 "unsafe" 21 22 "golang.org/x/sys/windows" 23 ) 24 25 const ( 26 logPixelsX = 88 27 monitorDefaultToNearest = 2 28 mdtEffectiveDpi = 0 29 ) 30 31 type rect struct { 32 left int32 33 top int32 34 right int32 35 bottom int32 36 } 37 38 var ( 39 user32 = windows.NewLazySystemDLL("user32") 40 gdi32 = windows.NewLazySystemDLL("gdi32") 41 shcore = windows.NewLazySystemDLL("shcore") 42 ) 43 44 var ( 45 procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware") 46 procGetWindowDC = user32.NewProc("GetWindowDC") 47 procReleaseDC = user32.NewProc("ReleaseDC") 48 procMonitorFromRect = user32.NewProc("MonitorFromRect") 49 procGetMonitorInfo = user32.NewProc("GetMonitorInfoW") 50 51 procGetDeviceCaps = gdi32.NewProc("GetDeviceCaps") 52 53 // GetScaleFactorForMonitor function can return unrelaiavle value (e.g. returning 180 54 // for 200% scale). Use GetDpiForMonitor instead. 55 procGetDpiForMonitor = shcore.NewProc("GetDpiForMonitor") 56 ) 57 58 var shcoreAvailable = false 59 60 type winErr struct { 61 FuncName string 62 Code windows.Errno 63 Return uintptr 64 } 65 66 func (e *winErr) Error() string { 67 return fmt.Sprintf("devicescale: %s failed: error code: %d", e.FuncName, e.Code) 68 } 69 70 func init() { 71 if shcore.Load() == nil { 72 shcoreAvailable = true 73 } 74 } 75 76 func setProcessDPIAware() error { 77 r, _, e := procSetProcessDPIAware.Call() 78 if e != nil && e.(windows.Errno) != 0 { 79 return &winErr{ 80 FuncName: "SetProcessDPIAware", 81 Code: e.(windows.Errno), 82 } 83 } 84 if r == 0 { 85 return &winErr{ 86 FuncName: "SetProcessDPIAware", 87 Return: r, 88 } 89 } 90 return nil 91 } 92 93 func getWindowDC(hwnd uintptr) (uintptr, error) { 94 r, _, e := procGetWindowDC.Call(hwnd) 95 if e != nil && e.(windows.Errno) != 0 { 96 return 0, &winErr{ 97 FuncName: "GetWindowDC", 98 Code: e.(windows.Errno), 99 } 100 } 101 if r == 0 { 102 return 0, &winErr{ 103 FuncName: "GetWindowDC", 104 Return: r, 105 } 106 } 107 return r, nil 108 } 109 110 func releaseDC(hwnd, hdc uintptr) error { 111 r, _, e := procReleaseDC.Call(hwnd, hdc) 112 if e != nil && e.(windows.Errno) != 0 { 113 return &winErr{ 114 FuncName: "ReleaseDC", 115 Code: e.(windows.Errno), 116 } 117 } 118 if r == 0 { 119 return &winErr{ 120 FuncName: "ReleaseDC", 121 Return: r, 122 } 123 } 124 return nil 125 } 126 127 func getDeviceCaps(hdc uintptr, nindex int) (int, error) { 128 r, _, e := procGetDeviceCaps.Call(hdc, uintptr(nindex)) 129 if e != nil && e.(windows.Errno) != 0 { 130 return 0, &winErr{ 131 FuncName: "GetDeviceCaps", 132 Code: e.(windows.Errno), 133 } 134 } 135 return int(r), nil 136 } 137 138 func monitorFromRect(lprc *rect, dwFlags int) (uintptr, error) { 139 r, _, e := procMonitorFromRect.Call(uintptr(unsafe.Pointer(lprc)), uintptr(dwFlags)) 140 runtime.KeepAlive(lprc) 141 if e != nil && e.(windows.Errno) != 0 { 142 return 0, &winErr{ 143 FuncName: "MonitorFromRect", 144 Code: e.(windows.Errno), 145 } 146 } 147 if r == 0 { 148 return 0, &winErr{ 149 FuncName: "MonitorFromRect", 150 Return: r, 151 } 152 } 153 return r, nil 154 } 155 156 func getMonitorInfo(hMonitor uintptr, lpMonitorInfo uintptr) error { 157 r, _, e := procGetMonitorInfo.Call(hMonitor, lpMonitorInfo) 158 if e != nil && e.(windows.Errno) != 0 { 159 return &winErr{ 160 FuncName: "GetMonitorInfo", 161 Code: e.(windows.Errno), 162 } 163 } 164 if r == 0 { 165 return &winErr{ 166 FuncName: "GetMonitorInfo", 167 Return: r, 168 } 169 } 170 return nil 171 } 172 173 func getDpiForMonitor(hMonitor uintptr, dpiType uintptr, dpiX, dpiY *uint32) error { 174 r, _, e := procGetDpiForMonitor.Call(hMonitor, dpiType, uintptr(unsafe.Pointer(dpiX)), uintptr(unsafe.Pointer(dpiY))) 175 if e != nil && e.(windows.Errno) != 0 { 176 return &winErr{ 177 FuncName: "GetDpiForMonitor", 178 Code: e.(windows.Errno), 179 } 180 } 181 if r != 0 { 182 return &winErr{ 183 FuncName: "GetDpiForMonitor", 184 Return: r, 185 } 186 } 187 return nil 188 } 189 190 func getFromLogPixelSx() float64 { 191 dc, err := getWindowDC(0) 192 if err != nil { 193 const ( 194 errorInvalidWindowHandle = 1400 195 errorResourceDataNotFound = 1812 196 ) 197 // On Wine, it looks like GetWindowDC(0) doesn't work (#738, #743). 198 code := err.(*winErr).Code 199 if code == errorInvalidWindowHandle { 200 return 1 201 } 202 if code == errorResourceDataNotFound { 203 return 1 204 } 205 panic(err) 206 } 207 208 // Note that GetDeviceCaps with LOGPIXELSX always returns a same value for any monitors 209 // even if multiple monitors are used. 210 dpi, err := getDeviceCaps(dc, logPixelsX) 211 if err != nil { 212 panic(err) 213 } 214 215 if err := releaseDC(0, dc); err != nil { 216 panic(err) 217 } 218 219 return float64(dpi) / 96 220 } 221 222 func impl(x, y int) float64 { 223 if err := setProcessDPIAware(); err != nil { 224 panic(err) 225 } 226 227 // On Windows 7 or older, shcore.dll is not available. 228 if !shcoreAvailable { 229 return getFromLogPixelSx() 230 } 231 232 lprc := rect{ 233 left: int32(x), 234 right: int32(x + 1), 235 top: int32(y), 236 bottom: int32(y + 1), 237 } 238 239 // MonitorFromPoint requires to pass a POINT value, and there seems no portable way to 240 // do this with Cgo. Use MonitorFromRect instead. 241 m, err := monitorFromRect(&lprc, monitorDefaultToNearest) 242 if err != nil { 243 panic(err) 244 } 245 246 dpiX := uint32(0) 247 dpiY := uint32(0) // Passing dpiY is needed even though this is not used, or GetDpiForMonitor returns an error. 248 if err := getDpiForMonitor(m, mdtEffectiveDpi, &dpiX, &dpiY); err != nil { 249 panic(err) 250 } 251 runtime.KeepAlive(dpiY) 252 253 return float64(dpiX) / 96 254 }