diff --git a/internal/devicescale/impl_unix.go b/internal/devicescale/impl_unix.go index 88178e86d..521dc741a 100644 --- a/internal/devicescale/impl_unix.go +++ b/internal/devicescale/impl_unix.go @@ -19,11 +19,14 @@ package devicescale import ( + "math" "os" "os/exec" "regexp" "strconv" "strings" + "sync/atomic" + "time" ) type desktop int @@ -37,6 +40,29 @@ const ( desktopXfce ) +var ( + cachedScale uint64 // use atomic to read/write as multiple goroutines touch it. + cacheUpdateWait = time.Millisecond * 100 +) + +func init() { + go scaleUpdater() +} + +func impl(x, y int) float64 { + return math.Float64frombits(atomic.LoadUint64(&cachedScale)) +} + +// run as goroutine. Will keep the desktop scale up to date. +// This can be removed once the scale change event is implemented in GLFW 3.3 +func scaleUpdater() { + for { + s := getscale(0, 0) + atomic.StoreUint64(&cachedScale, math.Float64bits(s)) + time.Sleep(cacheUpdateWait) + } +} + func currentDesktop() desktop { tokens := strings.Split(os.Getenv("XDG_CURRENT_DESKTOP"), ":") switch tokens[len(tokens)-1] { @@ -95,30 +121,24 @@ func cinnamonScale() float64 { return float64(s) } -func impl(x, y int) float64 { - // TODO: Can Linux has different scales for multiple monitors? +func getscale(x, y int) float64 { + s := -1.0 switch currentDesktop() { case desktopGnome: - s := gnomeScale() - if s <= 0 { - return 1 - } - return s + // TODO: Support wayland and per-monitor scaling https://wiki.gnome.org/HowDoI/HiDpi + s = gnomeScale() case desktopCinnamon: - s := cinnamonScale() - if s <= 0 { - return 1 - } - return s + s = cinnamonScale() case desktopUnity: - // TODO: Implement - return 1 + // TODO: Implement, supports per-monitor scaling case desktopKDE: - // TODO: Implement - return 1 + // TODO: Implement, appears to support per-monitor scaling case desktopXfce: // TODO: Implement - return 1 } - return 1 + if s <= 0 { + s = 1 + } + + return s } diff --git a/internal/ui/ui_glfw.go b/internal/ui/ui_glfw.go index aa32a4b8b..bde779d56 100644 --- a/internal/ui/ui_glfw.go +++ b/internal/ui/ui_glfw.go @@ -76,6 +76,10 @@ func init() { if err := initialize(); err != nil { panic(err) } + glfw.SetMonitorCallback(func(monitor *glfw.Monitor, event glfw.MonitorEvent) { + cacheMonitors() + }) + cacheMonitors() } func initialize() error { @@ -117,6 +121,44 @@ func initialize() error { return nil } +type cachedMonitor struct { + m *glfw.Monitor + vm *glfw.VidMode + // Pos of monitor in virtual coords + x int + y int +} + +// monitors is the monitor list cache for desktop glfw compile targets. +// populated by 'cacheMonitors' which is called on init and every +// monitor config change event. +var monitors []*cachedMonitor + +func cacheMonitors() { + monitors = make([]*cachedMonitor, 0, 3) + ms := glfw.GetMonitors() + for _, m := range ms { + x, y := m.GetPos() + monitors = append(monitors, &cachedMonitor{ + m: m, + vm: m.GetVideoMode(), + x: x, + y: y, + }) + } +} + +// getCachedMonitor returns a monitor for the given window x/y +// returns false if monitor is not found. +func getCachedMonitor(wx, wy int) (*cachedMonitor, bool) { + for _, m := range monitors { + if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height { + return m, true + } + } + return nil, false +} + func Loop(ch <-chan error) error { currentUI.setRunning(true) if err := mainthread.Loop(ch); err != nil { @@ -552,6 +594,10 @@ func (u *userInterface) getScale() float64 { // actualScreenScale must be called from the main thread. func (u *userInterface) actualScreenScale() float64 { + // Avoid calling monitor.GetPos if we have the monitor position cached already. + if cm, ok := getCachedMonitor(u.window.GetPos()); ok { + return u.getScale() * devicescale.GetAt(cm.x, cm.y) + } return u.getScale() * devicescale.GetAt(u.currentMonitor().GetPos()) } diff --git a/internal/ui/ui_unix.go b/internal/ui/ui_unix.go index 40d16f919..bc44bb14f 100644 --- a/internal/ui/ui_unix.go +++ b/internal/ui/ui_unix.go @@ -26,7 +26,11 @@ import ( func glfwScale() float64 { // This function must be called on the main thread. - return devicescale.GetAt(currentUI.currentMonitor().GetPos()) + cm, ok := getCachedMonitor(currentUI.window.GetPos()) + if !ok { + return devicescale.GetAt(currentUI.currentMonitor().GetPos()) + } + return devicescale.GetAt(cm.x, cm.y) } func adjustWindowPosition(x, y int) (int, int) { @@ -35,14 +39,8 @@ func adjustWindowPosition(x, y int) (int, int) { func (u *userInterface) currentMonitorImpl() *glfw.Monitor { // TODO: Return more appropriate display. - w := u.window - 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 - } + if cm, ok := getCachedMonitor(u.window.GetPos()); ok { + return cm.m } return glfw.GetPrimaryMonitor() }