zorldo

Goofing around with Ebiten
git clone git://bsandro.tech/zorldo
Log | Files | Refs

impl_windows.go (5943B)


      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 		// monitorFromRect can fail in some environments (#1612)
    244 		return getFromLogPixelSx()
    245 	}
    246 
    247 	dpiX := uint32(0)
    248 	dpiY := uint32(0) // Passing dpiY is needed even though this is not used, or GetDpiForMonitor returns an error.
    249 	if err := getDpiForMonitor(m, mdtEffectiveDpi, &dpiX, &dpiY); err != nil {
    250 		// getDpiForMonitor can fail in some environments (#1612)
    251 		return getFromLogPixelSx()
    252 	}
    253 	runtime.KeepAlive(dpiY)
    254 
    255 	return float64(dpiX) / 96
    256 }