From 396a538afe04d5b50f5582c3e2096d13fd182595 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 4 Nov 2019 12:32:06 +0100 Subject: [PATCH] app/internal/window: don't second guess UI scale Before this change, Gio tries hard to come up with a reasonable UI scale factor on desktop OSes derived from the physical dimensions and resolution of connected monitors. Gio also attempts to detect the user specified system UI scale and apply it. However, all that is complex and misguided: - The UI scale should not depend on whatever monitor is connected at program startup - For multiple monitors, it's unclear which one to base the scale off. - Applying both a monitor derived scale *and* the user specified scale is wrong, because the user scale is relative to some fixed scale, not Gio's derived scale. - With an automatic scale, Gio does not respect user preference and will not have a similar scale to other programs on the desktop. Get rid of the the automatic UI scale detection and rely only on the user scale. Updates gio#53 Signed-off-by: Elias Naur --- app/internal/window/os_js.go | 2 +- app/internal/window/os_macos.go | 32 ++++++++--------------------- app/internal/window/os_macos.h | 2 -- app/internal/window/os_macos.m | 19 ----------------- app/internal/window/os_wayland.go | 32 ++++++----------------------- app/internal/window/os_windows.go | 34 ++++++++++++++++++++----------- app/internal/window/window.go | 9 -------- 7 files changed, 38 insertions(+), 92 deletions(-) diff --git a/app/internal/window/os_js.go b/app/internal/window/os_js.go index 588308fe..66772f58 100644 --- a/app/internal/window/os_js.go +++ b/app/internal/window/os_js.go @@ -370,7 +370,7 @@ func (w *window) config() (int, int, float32, config) { w.cnv.Set("width", iw) w.cnv.Set("height", ih) } - const ppdp = 96 * inchPrDp * monitorScale + const ppdp = 96 * inchPrDp return iw, ih, float32(scale), config{ pxPerDp: ppdp * float32(scale), pxPerSp: ppdp * float32(scale), diff --git a/app/internal/window/os_macos.go b/app/internal/window/os_macos.go index 71c39ca7..ed4edb2c 100644 --- a/app/internal/window/os_macos.go +++ b/app/internal/window/os_macos.go @@ -36,7 +36,6 @@ type window struct { view C.CFTypeRef w Callbacks stage system.Stage - ppdp float32 scale float32 } @@ -198,7 +197,7 @@ func (w *window) draw(sync bool) { } width := int(wf*w.scale + .5) height := int(hf*w.scale + .5) - cfg := configFor(w.ppdp, w.scale) + cfg := configFor(w.scale) cfg.now = time.Now() w.setStage(system.StageRunning) w.w.Event(FrameEvent{ @@ -213,20 +212,10 @@ func (w *window) draw(sync bool) { }) } -func getPixelsPerDp(scale float32) float32 { - ppdp := float32(C.gio_getPixelsPerDP()) - ppdp = ppdp * scale * monitorScale - if ppdp < minDensity { - ppdp = minDensity - } - return ppdp / scale -} - -func configFor(ppdp, scale float32) config { - ppdp = ppdp * scale +func configFor(scale float32) config { return config{ - pxPerDp: ppdp, - pxPerSp: ppdp, + pxPerDp: scale, + pxPerSp: scale, } } @@ -258,10 +247,9 @@ func gio_onShow(view C.CFTypeRef) { //export gio_onCreate func gio_onCreate(view C.CFTypeRef) { viewDo(view, func(views viewMap, view C.CFTypeRef) { - scale := float32(C.gio_getBackingScale()) + scale := float32(C.gio_getViewBackingScale(view)) w := &window{ view: view, - ppdp: getPixelsPerDp(scale), scale: scale, } wopts := <-mainWindow.out @@ -283,15 +271,13 @@ func Main() { // TODO: return this error from CreateWindow. panic(errors.New("CreateWindow: failed to create view")) } - scale := float32(C.gio_getBackingScale()) - ppdp := getPixelsPerDp(scale) - cfg := configFor(ppdp, scale) + // Window sizes is in unscaled screen coordinates, not device pixels. + cfg := configFor(1.0) opts := wopts.opts w := cfg.Px(opts.Width) h := cfg.Px(opts.Height) - // Window sizes is on screen coordinates, not device pixels. - w = int(float32(w) / scale) - h = int(float32(h) / scale) + w = int(float32(w)) + h = int(float32(h)) title := C.CString(opts.Title) defer C.free(unsafe.Pointer(title)) C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h)) diff --git a/app/internal/window/os_macos.h b/app/internal/window/os_macos.h index 0e17d34b..201b6d04 100644 --- a/app/internal/window/os_macos.h +++ b/app/internal/window/os_macos.h @@ -12,8 +12,6 @@ __attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef) __attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(CFTypeRef viewRef); __attribute__ ((visibility ("hidden"))) void gio_setAnimating(CFTypeRef viewRef, BOOL anim); __attribute__ ((visibility ("hidden"))) void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID); -__attribute__ ((visibility ("hidden"))) CGFloat gio_getPixelsPerDP(void); -__attribute__ ((visibility ("hidden"))) CGFloat gio_getBackingScale(void); __attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef); #endif diff --git a/app/internal/window/os_macos.m b/app/internal/window/os_macos.m index 8ebfd8f3..35d98c52 100644 --- a/app/internal/window/os_macos.m +++ b/app/internal/window/os_macos.m @@ -57,25 +57,6 @@ CGFloat gio_viewWidth(CFTypeRef viewRef) { return [view bounds].size.width; } -// Points pr. dp. -static CGFloat getPointsPerDP(NSScreen *screen) { - NSDictionary *description = [screen deviceDescription]; - NSSize displayPixelSize = [[description objectForKey:NSDeviceSize] sizeValue]; - CGSize displayPhysicalSize = CGDisplayScreenSize([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]); - return (25.4/160)*displayPixelSize.width / displayPhysicalSize.width; -} - -// Pixels pr dp. -CGFloat gio_getPixelsPerDP(void) { - NSScreen *screen = [NSScreen mainScreen]; - return getPointsPerDP(screen); -} - -CGFloat gio_getBackingScale() { - NSScreen *screen = [NSScreen mainScreen]; - return [screen backingScaleFactor]; -} - CGFloat gio_getViewBackingScale(CFTypeRef viewRef) { NSView *view = (__bridge NSView *)viewRef; return [view.window backingScaleFactor]; diff --git a/app/internal/window/os_wayland.go b/app/internal/window/os_wayland.go index 2b3e79ad..ed362464 100644 --- a/app/internal/window/os_wayland.go +++ b/app/internal/window/os_wayland.go @@ -187,21 +187,13 @@ func createNativeWindow(opts *Options) (*window, error) { return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err) } - fontScale := detectFontScale() - var ppmm float32 var scale int for _, conf := range outputConfig { - if d, err := conf.ppmm(); err == nil && d > ppmm { - ppmm = d - } if s := conf.scale; s > scale { scale = s } } - ppdp := ppmm * mmPrDp * fontScale - if ppdp < minDensity { - ppdp = minDensity - } + ppdp := detectUIScale() w := &window{ disp: conn.disp, @@ -893,17 +885,6 @@ func gio_onTextInputDeleteSurroundingText(data unsafe.Pointer, im *C.struct_zwp_ func gio_onTextInputDone(data unsafe.Pointer, im *C.struct_zwp_text_input_v3, serial C.uint32_t) { } -// ppmm returns the approximate pixels per millimeter for the output. -func (c *wlOutput) ppmm() (float32, error) { - if c.physWidth == 0 || c.physHeight == 0 { - return 0, errors.New("no physical size data for output") - } - // Because of https://gitlab.gnome.org/GNOME/mutter/issues/369, output dimensions might be undetectably swapped. - // Instead, compute and return sqrt(px²/mm²). - density := float32(math.Sqrt(float64(c.width*c.height) / float64(c.physWidth*c.physHeight))) - return density, nil -} - func (w *window) flushScroll() { var fling f32.Point if w.fling.anim.Active() { @@ -911,7 +892,7 @@ func (w *window) flushScroll() { fling = w.fling.dir.Mul(dist) } // The Wayland reported scroll distance for - // discrete scroll axis is only 10 pixels, where + // discrete scroll axes is only 10 pixels, where // 100 seems more appropriate. const discreteScale = 10 if w.scroll.steps.X != 0 { @@ -1066,17 +1047,16 @@ func (w *window) surface() (*C.struct_wl_surface, int, int) { func (w *window) ShowTextInput(show bool) {} -// detectFontScale reports the system font scale, or monitorScale -// if it fails. -func detectFontScale() float32 { +// detectUIScale reports the system UI scale, or 1.0 if it fails. +func detectUIScale() float32 { // TODO: What about other window environments? out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output() if err != nil { - return monitorScale + return 1.0 } scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32) if err != nil { - return monitorScale + return 1.0 } return float32(scale) } diff --git a/app/internal/window/os_windows.go b/app/internal/window/os_windows.go index b9fcaf89..f594a6c4 100644 --- a/app/internal/window/os_windows.go +++ b/app/internal/window/os_windows.go @@ -79,7 +79,9 @@ const ( _INFINITE = 0xFFFFFFFF - _LOGPIXELSX = 88 + _MDT_EFFECTIVE_DPI = 0 + + _MONITOR_DEFAULTTOPRIMARY = 1 _SIZE_MAXIMIZED = 2 _SIZE_MINIMIZED = 1 @@ -110,6 +112,7 @@ const ( _WM_CANCELMODE = 0x001F _WM_CHAR = 0x0102 _WM_CREATE = 0x0001 + _WM_DPICHANGED = 0x02E0 _WM_DESTROY = 0x0002 _WM_KEYDOWN = 0x0100 _WM_KEYUP = 0x0101 @@ -270,6 +273,9 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr } // The message is processed. return 1 + case _WM_DPICHANGED: + // Let Windows know we're prepared for runtime DPI changes. + return 1 case _WM_KEYDOWN, _WM_SYSKEYDOWN: if n, ok := convertKeyCode(wParam); ok { cmd := key.Event{Name: n} @@ -478,12 +484,9 @@ func convertKeyCode(code uintptr) (rune, bool) { } func configForDC(hdc syscall.Handle) config { - dpi := getDeviceCaps(hdc, _LOGPIXELSX) - ppdp := float32(dpi) * inchPrDp * monitorScale - // Force a minimum density to keep text legible and to handle bogus output geometry. - if ppdp < minDensity { - ppdp = minDensity - } + hmon := monitorFromPoint(point{}, _MONITOR_DEFAULTTOPRIMARY) + dpi := getDpiForMonitor(hmon, _MDT_EFFECTIVE_DPI) + ppdp := float32(dpi) * inchPrDp return config{ pxPerDp: ppdp, pxPerSp: ppdp, @@ -508,6 +511,7 @@ var ( _GetMessageTime = user32.NewProc("GetMessageTime") _KillTimer = user32.NewProc("KillTimer") _LoadCursor = user32.NewProc("LoadCursorW") + _MonitorFromPoint = user32.NewProc("MonitorFromPoint") _MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx") _PeekMessage = user32.NewProc("PeekMessageW") _PostMessage = user32.NewProc("PostMessageW") @@ -526,8 +530,8 @@ var ( _UnregisterClass = user32.NewProc("UnregisterClassW") _UpdateWindow = user32.NewProc("UpdateWindow") - gdi32 = syscall.NewLazySystemDLL("gdi32") - _GetDeviceCaps = gdi32.NewProc("GetDeviceCaps") + shcore = syscall.NewLazySystemDLL("shcore") + _GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor") ) func getModuleHandle() (syscall.Handle, error) { @@ -596,9 +600,10 @@ func getDC(hwnd syscall.Handle) (syscall.Handle, error) { return syscall.Handle(hdc), nil } -func getDeviceCaps(hdc syscall.Handle, index int32) int { - c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index)) - return int(c) +func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int { + var dpiX, dpiY uintptr + _GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY))) + return int(dpiX) } func getKeyState(nVirtKey int32) int16 { @@ -636,6 +641,11 @@ func loadCursor(curID uint16) (syscall.Handle, error) { return syscall.Handle(h), nil } +func monitorFromPoint(pt point, flags uint32) syscall.Handle { + r, _, _ := _MonitorFromPoint.Call(uintptr(pt.x), uintptr(pt.y), uintptr(flags)) + return syscall.Handle(r) +} + func msgWaitForMultipleObjectsEx(nCount uint32, pHandles uintptr, millis, mask, flags uint32) (uint32, error) { r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags)) res := uint32(r) diff --git a/app/internal/window/window.go b/app/internal/window/window.go index 4c26ad91..258ebe52 100644 --- a/app/internal/window/window.go +++ b/app/internal/window/window.go @@ -109,13 +109,4 @@ func newWindowRendezvous() *windowRendezvous { const ( inchPrDp = 1.0 / 160 - mmPrDp = 25.4 / 160 - // monitorScale is the extra scale applied to - // monitor outputs to compensate for the extra - // viewing distance compared to phone and tables. - monitorScale = 1.20 - // minDensity is the minimum pixels per dp to - // ensure font and ui legibility on low-dpi - // screens. - minDensity = 1.0 )