From e0d2d5e75311bafa64e687fade016f6fe089b853 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 6 Oct 2018 22:42:28 +0900 Subject: [PATCH] ui: Bug fix: wrong scaling when a window move across monitors Fixes #701 --- internal/devicescale/impl_windows.go | 3 +- internal/ui/ui_glfw.go | 12 +-- internal/ui/ui_mac.go | 32 ++++++++ internal/ui/ui_unix.go | 16 ++++ internal/ui/ui_windows.go | 118 ++++++++++++++++++++++++--- 5 files changed, 157 insertions(+), 24 deletions(-) diff --git a/internal/devicescale/impl_windows.go b/internal/devicescale/impl_windows.go index 45a5ecee0..e1292acd6 100644 --- a/internal/devicescale/impl_windows.go +++ b/internal/devicescale/impl_windows.go @@ -24,7 +24,6 @@ import ( const ( logPixelsX = 88 - monitorDefaultToNull = 0 monitorDefaultToNearest = 2 mdtEffectiveDpi = 0 ) @@ -178,7 +177,7 @@ func impl(x, y int) float64 { // MonitorFromPoint requires to pass a POINT value, and there seems no portable way to // do this with Cgo. Use MonitorFromRect instead. - m, err := monitorFromRect(uintptr(unsafe.Pointer(&lprc)), monitorDefaultToNull) + m, err := monitorFromRect(uintptr(unsafe.Pointer(&lprc)), monitorDefaultToNearest) if err != nil { panic(err) } diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index 711b537f4..b71d66b43 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -774,14 +774,6 @@ func (u *userInterface) currentMonitor() *glfw.Monitor { if m := w.GetMonitor(); m != nil { return m } - - wx, wy := w.GetPos() - for _, m := range glfw.GetMonitors() { - mx, my := m.GetPos() - v := m.GetVideoMode() - if mx <= wx && wx < mx+v.Width && my <= wy && wy < my+v.Height { - return m - } - } - return glfw.GetPrimaryMonitor() + // Get the monitor which the current window belongs to. This requires OS API. + return currentMonitor() } diff --git a/internal/ui/ui_mac.go b/internal/ui/ui_mac.go index e297e11bd..2411c367a 100644 --- a/internal/ui/ui_mac.go +++ b/internal/ui/ui_mac.go @@ -18,6 +18,25 @@ package ui +// #cgo CFLAGS: -x objective-c +// #cgo LDFLAGS: -framework AppKit +// +// #import +// +// static void currentMonitorPos(int* x, int* y) { +// NSDictionary* screenDictionary = [[NSScreen mainScreen] deviceDescription]; +// NSNumber* screenID = [screenDictionary objectForKey:@"NSScreenNumber"]; +// CGDirectDisplayID aID = [screenID unsignedIntValue]; +// const CGRect bounds = CGDisplayBounds(aID); +// *x = bounds.origin.x; +// *y = bounds.origin.y; +// } +import "C" + +import ( + "github.com/go-gl/glfw/v3.2/glfw" +) + func glfwScale() float64 { return 1 } @@ -25,3 +44,16 @@ func glfwScale() float64 { func adjustWindowPosition(x, y int) (int, int) { return x, y } + +func currentMonitor() *glfw.Monitor { + x := C.int(0) + y := C.int(0) + C.currentMonitorPos(&x, &y) + for _, m := range glfw.GetMonitors() { + mx, my := m.GetPos() + if int(x) == mx && int(y) == my { + return m + } + } + return nil +} diff --git a/internal/ui/ui_unix.go b/internal/ui/ui_unix.go index 02c8c0d64..75103b8b1 100644 --- a/internal/ui/ui_unix.go +++ b/internal/ui/ui_unix.go @@ -19,6 +19,8 @@ package ui import ( + "github.com/go-gl/glfw/v3.2/glfw" + "github.com/hajimehoshi/ebiten/internal/devicescale" ) @@ -30,3 +32,17 @@ func glfwScale() float64 { func adjustWindowPosition(x, y int) (int, int) { return x, y } + +func currentMonitor() *glfw.Monitor { + // TODO: Return more appropriate display. + w := glfw.GetCurrentContext() + wx, wy := w.GetPos() + for _, m := range glfw.GetMonitors() { + mx, my := m.GetPos() + v := m.GetVideoMode() + if mx <= wx && wx < mx+v.Width && my <= wy && wy < my+v.Height { + return m + } + } + return nil +} diff --git a/internal/ui/ui_windows.go b/internal/ui/ui_windows.go index 8596b8cbd..474786a85 100644 --- a/internal/ui/ui_windows.go +++ b/internal/ui/ui_windows.go @@ -16,21 +16,81 @@ package ui -// TODO: Use golang.org/x/sys/windows (NewLazyDLL) instead of cgo. - -// #cgo LDFLAGS: -lgdi32 -// -// #include -// -// static int getCaptionHeight() { -// return GetSystemMetrics(SM_CYCAPTION); -// } -import "C" - import ( + "fmt" + "syscall" + "unsafe" + + "github.com/go-gl/glfw/v3.2/glfw" + "github.com/hajimehoshi/ebiten/internal/devicescale" ) +const ( + smCyCaption = 4 + monitorDefaultToNearest = 2 +) + +type rect struct { + left int32 + top int32 + right int32 + bottom int32 +} + +type monitorInfo struct { + cbSize uint32 + rcMonitor rect + rcWork rect + dwFlags uint32 +} + +var ( + // user32 is defined at hideconsole_windows.go + procGetSystemMetrics = user32.NewProc("GetSystemMetrics") + procGetActiveWindow = user32.NewProc("GetActiveWindow") + procMonitorFromWindow = user32.NewProc("MonitorFromWindow") + procGetMonitorInfoW = user32.NewProc("GetMonitorInfoW") +) + +func getSystemMetrics(nIndex int) (int, error) { + r, _, e := syscall.Syscall(procGetSystemMetrics.Addr(), 1, uintptr(nIndex), 0, 0) + if e != 0 { + return 0, fmt.Errorf("ui: GetSystemMetrics failed: error code: %d", e) + } + return int(r), nil +} + +func getActiveWindow() (uintptr, error) { + r, _, e := syscall.Syscall(procGetActiveWindow.Addr(), 0, 0, 0, 0) + if e != 0 { + return 0, fmt.Errorf("ui: GetActiveWindow failed: error code: %d", e) + } + return r, nil +} + +func monitorFromWindow(hwnd uintptr, dwFlags uint32) (uintptr, error) { + r, _, e := syscall.Syscall(procMonitorFromWindow.Addr(), 2, hwnd, uintptr(dwFlags), 0) + if e != 0 { + return 0, fmt.Errorf("ui: MonitorFromWindow failed: error code: %d", e) + } + if r == 0 { + return 0, fmt.Errorf("ui: MonitorFromWindow failed: returned value: %d", r) + } + return r, nil +} + +func getMonitorInfoW(hMonitor uintptr, lpmi *monitorInfo) error { + r, _, e := syscall.Syscall(procGetMonitorInfoW.Addr(), 2, hMonitor, uintptr(unsafe.Pointer(lpmi)), 0) + if e != 0 { + return fmt.Errorf("ui: GetMonitorInfoW failed: error code: %d", e) + } + if r == 0 { + return fmt.Errorf("ui: GetMonitorInfoW failed: returned value: %d", r) + } + return nil +} + func glfwScale() float64 { // This function must be called on the main thread. return devicescale.GetAt(currentUI.currentMonitor().GetPos()) @@ -42,9 +102,43 @@ func adjustWindowPosition(x, y int) (int, int) { if x < 0 { x = 0 } - t := int(C.getCaptionHeight()) + t, err := getSystemMetrics(smCyCaption) + if err != nil { + panic(err) + } if y < t { y = t } return x, y } + +func currentMonitor() *glfw.Monitor { + w, err := getActiveWindow() + if err != nil { + panic(err) + } + if w == 0 { + // There is no window at launching. + return glfw.GetPrimaryMonitor() + } + + m, err := monitorFromWindow(w, monitorDefaultToNearest) + if err != nil { + panic(err) + } + + mi := monitorInfo{} + mi.cbSize = uint32(unsafe.Sizeof(mi)) + if err := getMonitorInfoW(m, &mi); err != nil { + panic(err) + } + + x, y := int(mi.rcMonitor.left), int(mi.rcMonitor.top) + for _, m := range glfw.GetMonitors() { + mx, my := m.GetPos() + if mx == x && my == y { + return m + } + } + return nil +}