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 <mail@eliasnaur.com>
This commit is contained in:
Elias Naur
2019-11-04 12:32:06 +01:00
parent 65146a5337
commit 396a538afe
7 changed files with 38 additions and 92 deletions
+1 -1
View File
@@ -370,7 +370,7 @@ func (w *window) config() (int, int, float32, config) {
w.cnv.Set("width", iw) w.cnv.Set("width", iw)
w.cnv.Set("height", ih) w.cnv.Set("height", ih)
} }
const ppdp = 96 * inchPrDp * monitorScale const ppdp = 96 * inchPrDp
return iw, ih, float32(scale), config{ return iw, ih, float32(scale), config{
pxPerDp: ppdp * float32(scale), pxPerDp: ppdp * float32(scale),
pxPerSp: ppdp * float32(scale), pxPerSp: ppdp * float32(scale),
+9 -23
View File
@@ -36,7 +36,6 @@ type window struct {
view C.CFTypeRef view C.CFTypeRef
w Callbacks w Callbacks
stage system.Stage stage system.Stage
ppdp float32
scale float32 scale float32
} }
@@ -198,7 +197,7 @@ func (w *window) draw(sync bool) {
} }
width := int(wf*w.scale + .5) width := int(wf*w.scale + .5)
height := int(hf*w.scale + .5) height := int(hf*w.scale + .5)
cfg := configFor(w.ppdp, w.scale) cfg := configFor(w.scale)
cfg.now = time.Now() cfg.now = time.Now()
w.setStage(system.StageRunning) w.setStage(system.StageRunning)
w.w.Event(FrameEvent{ w.w.Event(FrameEvent{
@@ -213,20 +212,10 @@ func (w *window) draw(sync bool) {
}) })
} }
func getPixelsPerDp(scale float32) float32 { func configFor(scale float32) config {
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
return config{ return config{
pxPerDp: ppdp, pxPerDp: scale,
pxPerSp: ppdp, pxPerSp: scale,
} }
} }
@@ -258,10 +247,9 @@ func gio_onShow(view C.CFTypeRef) {
//export gio_onCreate //export gio_onCreate
func gio_onCreate(view C.CFTypeRef) { func gio_onCreate(view C.CFTypeRef) {
viewDo(view, func(views viewMap, view C.CFTypeRef) { viewDo(view, func(views viewMap, view C.CFTypeRef) {
scale := float32(C.gio_getBackingScale()) scale := float32(C.gio_getViewBackingScale(view))
w := &window{ w := &window{
view: view, view: view,
ppdp: getPixelsPerDp(scale),
scale: scale, scale: scale,
} }
wopts := <-mainWindow.out wopts := <-mainWindow.out
@@ -283,15 +271,13 @@ func Main() {
// TODO: return this error from CreateWindow. // TODO: return this error from CreateWindow.
panic(errors.New("CreateWindow: failed to create view")) panic(errors.New("CreateWindow: failed to create view"))
} }
scale := float32(C.gio_getBackingScale()) // Window sizes is in unscaled screen coordinates, not device pixels.
ppdp := getPixelsPerDp(scale) cfg := configFor(1.0)
cfg := configFor(ppdp, scale)
opts := wopts.opts opts := wopts.opts
w := cfg.Px(opts.Width) w := cfg.Px(opts.Width)
h := cfg.Px(opts.Height) h := cfg.Px(opts.Height)
// Window sizes is on screen coordinates, not device pixels. w = int(float32(w))
w = int(float32(w) / scale) h = int(float32(h))
h = int(float32(h) / scale)
title := C.CString(opts.Title) title := C.CString(opts.Title)
defer C.free(unsafe.Pointer(title)) defer C.free(unsafe.Pointer(title))
C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h)) C.gio_main(view, title, C.CGFloat(w), C.CGFloat(h))
-2
View File
@@ -12,8 +12,6 @@ __attribute__ ((visibility ("hidden"))) CGFloat gio_viewWidth(CFTypeRef viewRef)
__attribute__ ((visibility ("hidden"))) CGFloat gio_viewHeight(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_setAnimating(CFTypeRef viewRef, BOOL anim);
__attribute__ ((visibility ("hidden"))) void gio_updateDisplayLink(CFTypeRef viewRef, CGDirectDisplayID dispID); __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); __attribute__ ((visibility ("hidden"))) CGFloat gio_getViewBackingScale(CFTypeRef viewRef);
#endif #endif
-19
View File
@@ -57,25 +57,6 @@ CGFloat gio_viewWidth(CFTypeRef viewRef) {
return [view bounds].size.width; 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) { CGFloat gio_getViewBackingScale(CFTypeRef viewRef) {
NSView *view = (__bridge NSView *)viewRef; NSView *view = (__bridge NSView *)viewRef;
return [view.window backingScaleFactor]; return [view.window backingScaleFactor];
+6 -26
View File
@@ -187,21 +187,13 @@ func createNativeWindow(opts *Options) (*window, error) {
return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err) return nil, fmt.Errorf("createNativeWindow: failed to create pipe: %v", err)
} }
fontScale := detectFontScale()
var ppmm float32
var scale int var scale int
for _, conf := range outputConfig { for _, conf := range outputConfig {
if d, err := conf.ppmm(); err == nil && d > ppmm {
ppmm = d
}
if s := conf.scale; s > scale { if s := conf.scale; s > scale {
scale = s scale = s
} }
} }
ppdp := ppmm * mmPrDp * fontScale ppdp := detectUIScale()
if ppdp < minDensity {
ppdp = minDensity
}
w := &window{ w := &window{
disp: conn.disp, 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) { 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() { func (w *window) flushScroll() {
var fling f32.Point var fling f32.Point
if w.fling.anim.Active() { if w.fling.anim.Active() {
@@ -911,7 +892,7 @@ func (w *window) flushScroll() {
fling = w.fling.dir.Mul(dist) fling = w.fling.dir.Mul(dist)
} }
// The Wayland reported scroll distance for // 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. // 100 seems more appropriate.
const discreteScale = 10 const discreteScale = 10
if w.scroll.steps.X != 0 { 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) {} func (w *window) ShowTextInput(show bool) {}
// detectFontScale reports the system font scale, or monitorScale // detectUIScale reports the system UI scale, or 1.0 if it fails.
// if it fails. func detectUIScale() float32 {
func detectFontScale() float32 {
// TODO: What about other window environments? // TODO: What about other window environments?
out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output() out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "text-scaling-factor").Output()
if err != nil { if err != nil {
return monitorScale return 1.0
} }
scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32) scale, err := strconv.ParseFloat(string(bytes.TrimSpace(out)), 32)
if err != nil { if err != nil {
return monitorScale return 1.0
} }
return float32(scale) return float32(scale)
} }
+22 -12
View File
@@ -79,7 +79,9 @@ const (
_INFINITE = 0xFFFFFFFF _INFINITE = 0xFFFFFFFF
_LOGPIXELSX = 88 _MDT_EFFECTIVE_DPI = 0
_MONITOR_DEFAULTTOPRIMARY = 1
_SIZE_MAXIMIZED = 2 _SIZE_MAXIMIZED = 2
_SIZE_MINIMIZED = 1 _SIZE_MINIMIZED = 1
@@ -110,6 +112,7 @@ const (
_WM_CANCELMODE = 0x001F _WM_CANCELMODE = 0x001F
_WM_CHAR = 0x0102 _WM_CHAR = 0x0102
_WM_CREATE = 0x0001 _WM_CREATE = 0x0001
_WM_DPICHANGED = 0x02E0
_WM_DESTROY = 0x0002 _WM_DESTROY = 0x0002
_WM_KEYDOWN = 0x0100 _WM_KEYDOWN = 0x0100
_WM_KEYUP = 0x0101 _WM_KEYUP = 0x0101
@@ -270,6 +273,9 @@ func windowProc(hwnd syscall.Handle, msg uint32, wParam, lParam uintptr) uintptr
} }
// The message is processed. // The message is processed.
return 1 return 1
case _WM_DPICHANGED:
// Let Windows know we're prepared for runtime DPI changes.
return 1
case _WM_KEYDOWN, _WM_SYSKEYDOWN: case _WM_KEYDOWN, _WM_SYSKEYDOWN:
if n, ok := convertKeyCode(wParam); ok { if n, ok := convertKeyCode(wParam); ok {
cmd := key.Event{Name: n} cmd := key.Event{Name: n}
@@ -478,12 +484,9 @@ func convertKeyCode(code uintptr) (rune, bool) {
} }
func configForDC(hdc syscall.Handle) config { func configForDC(hdc syscall.Handle) config {
dpi := getDeviceCaps(hdc, _LOGPIXELSX) hmon := monitorFromPoint(point{}, _MONITOR_DEFAULTTOPRIMARY)
ppdp := float32(dpi) * inchPrDp * monitorScale dpi := getDpiForMonitor(hmon, _MDT_EFFECTIVE_DPI)
// Force a minimum density to keep text legible and to handle bogus output geometry. ppdp := float32(dpi) * inchPrDp
if ppdp < minDensity {
ppdp = minDensity
}
return config{ return config{
pxPerDp: ppdp, pxPerDp: ppdp,
pxPerSp: ppdp, pxPerSp: ppdp,
@@ -508,6 +511,7 @@ var (
_GetMessageTime = user32.NewProc("GetMessageTime") _GetMessageTime = user32.NewProc("GetMessageTime")
_KillTimer = user32.NewProc("KillTimer") _KillTimer = user32.NewProc("KillTimer")
_LoadCursor = user32.NewProc("LoadCursorW") _LoadCursor = user32.NewProc("LoadCursorW")
_MonitorFromPoint = user32.NewProc("MonitorFromPoint")
_MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx") _MsgWaitForMultipleObjectsEx = user32.NewProc("MsgWaitForMultipleObjectsEx")
_PeekMessage = user32.NewProc("PeekMessageW") _PeekMessage = user32.NewProc("PeekMessageW")
_PostMessage = user32.NewProc("PostMessageW") _PostMessage = user32.NewProc("PostMessageW")
@@ -526,8 +530,8 @@ var (
_UnregisterClass = user32.NewProc("UnregisterClassW") _UnregisterClass = user32.NewProc("UnregisterClassW")
_UpdateWindow = user32.NewProc("UpdateWindow") _UpdateWindow = user32.NewProc("UpdateWindow")
gdi32 = syscall.NewLazySystemDLL("gdi32") shcore = syscall.NewLazySystemDLL("shcore")
_GetDeviceCaps = gdi32.NewProc("GetDeviceCaps") _GetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
) )
func getModuleHandle() (syscall.Handle, error) { func getModuleHandle() (syscall.Handle, error) {
@@ -596,9 +600,10 @@ func getDC(hwnd syscall.Handle) (syscall.Handle, error) {
return syscall.Handle(hdc), nil return syscall.Handle(hdc), nil
} }
func getDeviceCaps(hdc syscall.Handle, index int32) int { func getDpiForMonitor(hmonitor syscall.Handle, dpiType uint32) int {
c, _, _ := _GetDeviceCaps.Call(uintptr(hdc), uintptr(index)) var dpiX, dpiY uintptr
return int(c) _GetDpiForMonitor.Call(uintptr(hmonitor), uintptr(dpiType), uintptr(unsafe.Pointer(&dpiX)), uintptr(unsafe.Pointer(&dpiY)))
return int(dpiX)
} }
func getKeyState(nVirtKey int32) int16 { func getKeyState(nVirtKey int32) int16 {
@@ -636,6 +641,11 @@ func loadCursor(curID uint16) (syscall.Handle, error) {
return syscall.Handle(h), nil 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) { 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)) r, _, err := _MsgWaitForMultipleObjectsEx.Call(uintptr(nCount), pHandles, uintptr(millis), uintptr(mask), uintptr(flags))
res := uint32(r) res := uint32(r)
-9
View File
@@ -109,13 +109,4 @@ func newWindowRendezvous() *windowRendezvous {
const ( const (
inchPrDp = 1.0 / 160 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
) )