mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2025-01-12 20:18:59 +01:00
Switch out the devicescale implementation by one that relies on glfw/xrandr. (#1800)
This should fix fullscreen mode on Linux/X11 systems in general, while not affecting other systems. Note that this deletes a bunch of OS X specific and Windows specific code, as GLFW already provides this functionality. This change is not expected to cause regressions, however, the current behavior is still wrong and leads to wrong/unintended window sizes. To be fixed in further PRs. Updates #1774
This commit is contained in:
parent
cc1ac47387
commit
60df512352
1
go.mod
1
go.mod
@ -9,6 +9,7 @@ require (
|
|||||||
github.com/hajimehoshi/go-mp3 v0.3.2
|
github.com/hajimehoshi/go-mp3 v0.3.2
|
||||||
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc
|
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc
|
||||||
github.com/jakecoffman/cp v1.1.0
|
github.com/jakecoffman/cp v1.1.0
|
||||||
|
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
|
||||||
github.com/jfreymuth/oggvorbis v1.0.3
|
github.com/jfreymuth/oggvorbis v1.0.3
|
||||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||||
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5
|
golang.org/x/mobile v0.0.0-20210902104108-5d9a33257ab5
|
||||||
|
2
go.sum
2
go.sum
@ -13,6 +13,8 @@ github.com/hajimehoshi/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc h1:ztXP
|
|||||||
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ=
|
github.com/hajimehoshi/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc/go.mod h1:rUKQmwMkqmRxe+IAof9+tuYA2ofm8cAWXFmSfzDN8vQ=
|
||||||
github.com/jakecoffman/cp v1.1.0 h1:bhKvCNbAddYegYHSV5abG3G23vZdsISgqXa4X/lK8Oo=
|
github.com/jakecoffman/cp v1.1.0 h1:bhKvCNbAddYegYHSV5abG3G23vZdsISgqXa4X/lK8Oo=
|
||||||
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
|
github.com/jakecoffman/cp v1.1.0/go.mod h1:JjY/Fp6d8E1CHnu74gWNnU0+b9VzEdUVPoJxg2PsTQg=
|
||||||
|
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 h1:dy+DS31tGEGCsZzB45HmJJNHjur8GDgtRNX9U7HnSX4=
|
||||||
|
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240/go.mod h1:3P4UH/k22rXyHIJD2w4h2XMqPX4Of/eySEZq9L6wqc4=
|
||||||
github.com/jfreymuth/oggvorbis v1.0.3 h1:MLNGGyhOMiVcvea9Dp5+gbs2SAwqwQbtrWnonYa0M0Y=
|
github.com/jfreymuth/oggvorbis v1.0.3 h1:MLNGGyhOMiVcvea9Dp5+gbs2SAwqwQbtrWnonYa0M0Y=
|
||||||
github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
|
github.com/jfreymuth/oggvorbis v1.0.3/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
|
||||||
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
|
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
|
||||||
|
@ -1,152 +0,0 @@
|
|||||||
// Copyright 2020 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//go:build (dragonfly || freebsd || linux || netbsd || openbsd || solaris) && !android
|
|
||||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
// +build !android
|
|
||||||
|
|
||||||
package devicescale
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
|
||||||
)
|
|
||||||
|
|
||||||
type xmlBool bool
|
|
||||||
|
|
||||||
func (b *xmlBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|
||||||
var s string
|
|
||||||
if err := d.DecodeElement(&s, &start); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*b = xmlBool(s == "yes")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type cinnamonMonitors struct {
|
|
||||||
XMLName xml.Name `xml:"monitors"`
|
|
||||||
Version string `xml:"version,attr"`
|
|
||||||
Configuration []cinnamonMonitorsConfiguration `xml:"configuration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type cinnamonMonitorsConfiguration struct {
|
|
||||||
BaseScale float64 `xml:"base_scale"`
|
|
||||||
Output []struct {
|
|
||||||
X int `xml:"x"`
|
|
||||||
Y int `xml:"y"`
|
|
||||||
Width int `xml:"width"`
|
|
||||||
Height int `xml:"height"`
|
|
||||||
Scale float64 `xml:"scale"`
|
|
||||||
Primary xmlBool `xml:"primary"`
|
|
||||||
} `xml:"output"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cinnamonMonitorsConfiguration) matchesWithGLFWMonitors(monitors []*glfw.Monitor) bool {
|
|
||||||
type area struct {
|
|
||||||
X, Y, Width, Height int
|
|
||||||
}
|
|
||||||
areas := map[area]struct{}{}
|
|
||||||
|
|
||||||
for _, o := range c.Output {
|
|
||||||
if o.Width == 0 || o.Height == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
areas[area{
|
|
||||||
X: o.X,
|
|
||||||
Y: o.Y,
|
|
||||||
Width: o.Width,
|
|
||||||
Height: o.Height,
|
|
||||||
}] = struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(areas) != len(monitors) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, m := range monitors {
|
|
||||||
x, y := m.GetPos()
|
|
||||||
v := m.GetVideoMode()
|
|
||||||
a := area{
|
|
||||||
X: x,
|
|
||||||
Y: y,
|
|
||||||
Width: v.Width,
|
|
||||||
Height: v.Height,
|
|
||||||
}
|
|
||||||
if _, ok := areas[a]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func cinnamonScaleFromXML() (float64, error) {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
f, err := os.Open(filepath.Join(home, ".config", "cinnamon-monitors.xml"))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
d := xml.NewDecoder(f)
|
|
||||||
|
|
||||||
var monitors cinnamonMonitors
|
|
||||||
if err = d.Decode(&monitors); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range monitors.Configuration {
|
|
||||||
if !c.matchesWithGLFWMonitors(glfw.GetMonitors()) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, v := range c.Output {
|
|
||||||
// TODO: Get the monitor at the specified position.
|
|
||||||
// TODO: Consider the base scale?
|
|
||||||
if v.Primary && v.Scale != 0.0 {
|
|
||||||
return v.Scale, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func cinnamonScale() float64 {
|
|
||||||
if s, err := cinnamonScaleFromXML(); err == nil && s > 0 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
out, err := exec.Command("gsettings", "get", "org.cinnamon.desktop.interface", "scaling-factor").Output()
|
|
||||||
if err != nil {
|
|
||||||
if err == exec.ErrNotFound {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
m := gsettingsRe.FindStringSubmatch(string(out))
|
|
||||||
s, err := strconv.Atoi(m[1])
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return float64(s)
|
|
||||||
}
|
|
@ -28,7 +28,8 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// GetAt returns the device scale at (x, y).
|
// GetAt returns the device scale at (x, y).
|
||||||
// x and y are in device-dependent pixels.
|
// x and y are in device-dependent pixels and must be the top-left coordinate of a monitor, or 0,0 to request a "global scale".
|
||||||
|
// The device scale maps device dependent pixels to device independent pixels.
|
||||||
func GetAt(x, y int) float64 {
|
func GetAt(x, y int) float64 {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
|
35
internal/devicescale/impl_desktop.go
Normal file
35
internal/devicescale/impl_desktop.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2021 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !android && !ios && !js
|
||||||
|
// +build !android,!ios,!js
|
||||||
|
|
||||||
|
package devicescale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/glfw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func monitorAt(x, y int) *glfw.Monitor {
|
||||||
|
// Note: this assumes that x, y are exact monitor origins.
|
||||||
|
// If they're not, this arbitrarily returns the first monitor.
|
||||||
|
monitors := glfw.GetMonitors()
|
||||||
|
for _, mon := range monitors {
|
||||||
|
mx, my := mon.GetPos()
|
||||||
|
if x == mx && y == my {
|
||||||
|
return mon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return monitors[0]
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
// Copyright 2018 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//go:build darwin && !ios
|
|
||||||
// +build darwin,!ios
|
|
||||||
|
|
||||||
package devicescale
|
|
||||||
|
|
||||||
// #cgo CFLAGS: -x objective-c
|
|
||||||
// #cgo LDFLAGS: -framework AppKit
|
|
||||||
//
|
|
||||||
// #import <AppKit/AppKit.h>
|
|
||||||
//
|
|
||||||
// static float scaleAt(int x, int y) {
|
|
||||||
// // On macOS, the direction of Y axis is inverted from GLFW monitors (#807).
|
|
||||||
// // This is an inverse function of _glfwTransformYNS in GLFW (#1113).
|
|
||||||
// y = CGDisplayBounds(CGMainDisplayID()).size.height - y - 1;
|
|
||||||
//
|
|
||||||
// NSArray<NSScreen*>* screens = [NSScreen screens];
|
|
||||||
// for (NSScreen* screen in screens) {
|
|
||||||
// if (NSPointInRect(NSMakePoint(x, y), [screen frame])) {
|
|
||||||
// return [screen backingScaleFactor];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
|
||||||
return float64(C.scaleAt(C.int(x), C.int(y)))
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright 2018 The Ebiten Authors
|
// Copyright 2021 The Ebiten Authors
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
// you may not use this file except in compliance with the License.
|
// you may not use this file except in compliance with the License.
|
||||||
@ -12,8 +12,12 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build (darwin && !ios) || windows
|
||||||
|
// +build darwin,!ios windows
|
||||||
|
|
||||||
package devicescale
|
package devicescale
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
func impl(x, y int) float64 {
|
||||||
return 1
|
sx, _ := monitorAt(x, y).GetContentScale()
|
||||||
|
return float64(sx)
|
||||||
}
|
}
|
@ -1,101 +0,0 @@
|
|||||||
// Copyright 2018 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
//go:build (dragonfly || freebsd || linux || netbsd || openbsd || solaris) && !android
|
|
||||||
// +build dragonfly freebsd linux netbsd openbsd solaris
|
|
||||||
// +build !android
|
|
||||||
|
|
||||||
package devicescale
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type desktop int
|
|
||||||
|
|
||||||
const (
|
|
||||||
desktopUnknown desktop = iota
|
|
||||||
desktopGnome
|
|
||||||
desktopCinnamon
|
|
||||||
desktopUnity
|
|
||||||
desktopKDE
|
|
||||||
desktopXfce
|
|
||||||
)
|
|
||||||
|
|
||||||
func currentDesktop() desktop {
|
|
||||||
tokens := strings.Split(os.Getenv("XDG_CURRENT_DESKTOP"), ":")
|
|
||||||
switch tokens[len(tokens)-1] {
|
|
||||||
case "GNOME":
|
|
||||||
return desktopGnome
|
|
||||||
case "X-Cinnamon":
|
|
||||||
return desktopCinnamon
|
|
||||||
case "Unity":
|
|
||||||
return desktopUnity
|
|
||||||
case "KDE":
|
|
||||||
return desktopKDE
|
|
||||||
case "XFCE":
|
|
||||||
return desktopXfce
|
|
||||||
default:
|
|
||||||
return desktopUnknown
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var gsettingsRe = regexp.MustCompile(`\Auint32 (\d+)\s*\z`)
|
|
||||||
|
|
||||||
func gnomeScale() float64 {
|
|
||||||
// TODO: Should 'monitors.xml' be loaded?
|
|
||||||
|
|
||||||
out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "scaling-factor").Output()
|
|
||||||
if err != nil {
|
|
||||||
if err == exec.ErrNotFound {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
m := gsettingsRe.FindStringSubmatch(string(out))
|
|
||||||
s, err := strconv.Atoi(m[1])
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return float64(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
|
||||||
s := -1.0
|
|
||||||
switch currentDesktop() {
|
|
||||||
case desktopGnome:
|
|
||||||
// TODO: Support wayland and per-monitor scaling https://wiki.gnome.org/HowDoI/HiDpi
|
|
||||||
s = gnomeScale()
|
|
||||||
case desktopCinnamon:
|
|
||||||
s = cinnamonScale()
|
|
||||||
case desktopUnity:
|
|
||||||
// TODO: Implement, supports per-monitor scaling
|
|
||||||
case desktopKDE:
|
|
||||||
// TODO: Implement, appears to support per-monitor scaling
|
|
||||||
case desktopXfce:
|
|
||||||
// TODO: Implement
|
|
||||||
}
|
|
||||||
if s <= 0 {
|
|
||||||
s = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
@ -1,256 +0,0 @@
|
|||||||
// Copyright 2018 The Ebiten Authors
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package devicescale
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
logPixelsX = 88
|
|
||||||
monitorDefaultToNearest = 2
|
|
||||||
mdtEffectiveDpi = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
type rect struct {
|
|
||||||
left int32
|
|
||||||
top int32
|
|
||||||
right int32
|
|
||||||
bottom int32
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
user32 = windows.NewLazySystemDLL("user32")
|
|
||||||
gdi32 = windows.NewLazySystemDLL("gdi32")
|
|
||||||
shcore = windows.NewLazySystemDLL("shcore")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
procSetProcessDPIAware = user32.NewProc("SetProcessDPIAware")
|
|
||||||
procGetWindowDC = user32.NewProc("GetWindowDC")
|
|
||||||
procReleaseDC = user32.NewProc("ReleaseDC")
|
|
||||||
procMonitorFromRect = user32.NewProc("MonitorFromRect")
|
|
||||||
procGetMonitorInfo = user32.NewProc("GetMonitorInfoW")
|
|
||||||
|
|
||||||
procGetDeviceCaps = gdi32.NewProc("GetDeviceCaps")
|
|
||||||
|
|
||||||
// GetScaleFactorForMonitor function can return unrelaiavle value (e.g. returning 180
|
|
||||||
// for 200% scale). Use GetDpiForMonitor instead.
|
|
||||||
procGetDpiForMonitor = shcore.NewProc("GetDpiForMonitor")
|
|
||||||
)
|
|
||||||
|
|
||||||
var shcoreAvailable = false
|
|
||||||
|
|
||||||
type winErr struct {
|
|
||||||
FuncName string
|
|
||||||
Code windows.Errno
|
|
||||||
Return uintptr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *winErr) Error() string {
|
|
||||||
return fmt.Sprintf("devicescale: %s failed: error code: %d", e.FuncName, e.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
if shcore.Load() == nil {
|
|
||||||
shcoreAvailable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setProcessDPIAware() error {
|
|
||||||
r, _, e := procSetProcessDPIAware.Call()
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "SetProcessDPIAware",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "SetProcessDPIAware",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getWindowDC(hwnd uintptr) (uintptr, error) {
|
|
||||||
r, _, e := procGetWindowDC.Call(hwnd)
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return 0, &winErr{
|
|
||||||
FuncName: "GetWindowDC",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
return 0, &winErr{
|
|
||||||
FuncName: "GetWindowDC",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func releaseDC(hwnd, hdc uintptr) error {
|
|
||||||
r, _, e := procReleaseDC.Call(hwnd, hdc)
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "ReleaseDC",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "ReleaseDC",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeviceCaps(hdc uintptr, nindex int) (int, error) {
|
|
||||||
r, _, e := procGetDeviceCaps.Call(hdc, uintptr(nindex))
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return 0, &winErr{
|
|
||||||
FuncName: "GetDeviceCaps",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return int(r), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func monitorFromRect(lprc *rect, dwFlags int) (uintptr, error) {
|
|
||||||
r, _, e := procMonitorFromRect.Call(uintptr(unsafe.Pointer(lprc)), uintptr(dwFlags))
|
|
||||||
runtime.KeepAlive(lprc)
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return 0, &winErr{
|
|
||||||
FuncName: "MonitorFromRect",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
return 0, &winErr{
|
|
||||||
FuncName: "MonitorFromRect",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMonitorInfo(hMonitor uintptr, lpMonitorInfo uintptr) error {
|
|
||||||
r, _, e := procGetMonitorInfo.Call(hMonitor, lpMonitorInfo)
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "GetMonitorInfo",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r == 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "GetMonitorInfo",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDpiForMonitor(hMonitor uintptr, dpiType uintptr, dpiX, dpiY *uint32) error {
|
|
||||||
r, _, e := procGetDpiForMonitor.Call(hMonitor, dpiType, uintptr(unsafe.Pointer(dpiX)), uintptr(unsafe.Pointer(dpiY)))
|
|
||||||
if e != nil && e.(windows.Errno) != 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "GetDpiForMonitor",
|
|
||||||
Code: e.(windows.Errno),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r != 0 {
|
|
||||||
return &winErr{
|
|
||||||
FuncName: "GetDpiForMonitor",
|
|
||||||
Return: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFromLogPixelSx() float64 {
|
|
||||||
dc, err := getWindowDC(0)
|
|
||||||
if err != nil {
|
|
||||||
const (
|
|
||||||
errorInvalidWindowHandle = 1400
|
|
||||||
errorResourceDataNotFound = 1812
|
|
||||||
)
|
|
||||||
// On Wine, it looks like GetWindowDC(0) doesn't work (#738, #743).
|
|
||||||
code := err.(*winErr).Code
|
|
||||||
if code == errorInvalidWindowHandle {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
if code == errorResourceDataNotFound {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that GetDeviceCaps with LOGPIXELSX always returns a same value for any monitors
|
|
||||||
// even if multiple monitors are used.
|
|
||||||
dpi, err := getDeviceCaps(dc, logPixelsX)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := releaseDC(0, dc); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return float64(dpi) / 96
|
|
||||||
}
|
|
||||||
|
|
||||||
func impl(x, y int) float64 {
|
|
||||||
if err := setProcessDPIAware(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Windows 7 or older, shcore.dll is not available.
|
|
||||||
if !shcoreAvailable {
|
|
||||||
return getFromLogPixelSx()
|
|
||||||
}
|
|
||||||
|
|
||||||
lprc := rect{
|
|
||||||
left: int32(x),
|
|
||||||
right: int32(x + 1),
|
|
||||||
top: int32(y),
|
|
||||||
bottom: int32(y + 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
// MonitorFromPoint requires to pass a POINT value, and there seems no portable way to
|
|
||||||
// do this with Cgo. Use MonitorFromRect instead.
|
|
||||||
m, err := monitorFromRect(&lprc, monitorDefaultToNearest)
|
|
||||||
if err != nil {
|
|
||||||
// monitorFromRect can fail in some environments (#1612)
|
|
||||||
return getFromLogPixelSx()
|
|
||||||
}
|
|
||||||
|
|
||||||
dpiX := uint32(0)
|
|
||||||
dpiY := uint32(0) // Passing dpiY is needed even though this is not used, or GetDpiForMonitor returns an error.
|
|
||||||
if err := getDpiForMonitor(m, mdtEffectiveDpi, &dpiX, &dpiY); err != nil {
|
|
||||||
// getDpiForMonitor can fail in some environments (#1612)
|
|
||||||
return getFromLogPixelSx()
|
|
||||||
}
|
|
||||||
runtime.KeepAlive(dpiY)
|
|
||||||
|
|
||||||
return float64(dpiX) / 96
|
|
||||||
}
|
|
83
internal/devicescale/impl_x.go
Normal file
83
internal/devicescale/impl_x.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2021 The Ebiten Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
//go:build !android && !darwin && !js && !windows
|
||||||
|
// +build !android,!darwin,!js,!windows
|
||||||
|
|
||||||
|
package devicescale
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
|
"github.com/jezek/xgb"
|
||||||
|
"github.com/jezek/xgb/randr"
|
||||||
|
"github.com/jezek/xgb/xproto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func impl(x, y int) float64 {
|
||||||
|
// TODO: if https://github.com/glfw/glfw/issues/1961 gets fixed, this function may need revising.
|
||||||
|
// In case GLFW decides to switch to returning logical pixels, we can just return 1.0.
|
||||||
|
|
||||||
|
// Note: GLFW currently returns physical pixel sizes,
|
||||||
|
// but we need to predict the window system-side size of the fullscreen window
|
||||||
|
// for our `ScreenSizeInFullscreen` public API.
|
||||||
|
// Also at the moment we need this prior to switching to fullscreen, but that might be replacable.
|
||||||
|
// So this function computes the ratio of physical per logical pixels.
|
||||||
|
m := monitorAt(x, y)
|
||||||
|
sx, _ := m.GetContentScale()
|
||||||
|
monitorX, monitorY := m.GetPos()
|
||||||
|
xconn, err := xgb.NewConn()
|
||||||
|
defer xconn.Close()
|
||||||
|
if err != nil {
|
||||||
|
// No X11 connection?
|
||||||
|
// Assume we're on pure Wayland then.
|
||||||
|
// GLFW/Wayland shouldn't be having this issue.
|
||||||
|
return float64(sx)
|
||||||
|
}
|
||||||
|
if err = randr.Init(xconn); err != nil {
|
||||||
|
// No RANDR extension?
|
||||||
|
// No problem.
|
||||||
|
return float64(sx)
|
||||||
|
}
|
||||||
|
root := xproto.Setup(xconn).DefaultScreen(xconn).Root
|
||||||
|
res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply()
|
||||||
|
if err != nil {
|
||||||
|
// Likely means RANDR is not working.
|
||||||
|
// No problem.
|
||||||
|
return float64(sx)
|
||||||
|
}
|
||||||
|
for _, crtc := range res.Crtcs[:res.NumCrtcs] {
|
||||||
|
info, err := randr.GetCrtcInfo(xconn, crtc, res.ConfigTimestamp).Reply()
|
||||||
|
if err != nil {
|
||||||
|
// This Crtc is bad. Maybe just got disconnected?
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if info.NumOutputs == 0 {
|
||||||
|
// This Crtc is not connected to any output.
|
||||||
|
// In other words, a disabled monitor.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if int(info.X) == monitorX && int(info.Y) == monitorY {
|
||||||
|
xWidth, xHeight := info.Width, info.Height
|
||||||
|
vm := m.GetVideoMode()
|
||||||
|
physWidth, physHeight := vm.Width, vm.Height
|
||||||
|
// Return one scale, even though there may be separate X and Y scales.
|
||||||
|
// Return the _larger_ scale, as this would yield a letterboxed display on mismatch, rather than a cut-off one.
|
||||||
|
scale := math.Max(float64(physWidth)/float64(xWidth), float64(physHeight)/float64(xHeight))
|
||||||
|
return float64(sx) * scale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Monitor not known to XRandR. Weird.
|
||||||
|
return float64(sx)
|
||||||
|
}
|
@ -80,6 +80,13 @@ type Monitor struct {
|
|||||||
m uintptr
|
m uintptr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Monitor) GetContentScale() (float32, float32) {
|
||||||
|
var sx, sy float32
|
||||||
|
glfwDLL.call("glfwGetMonitorContentScale", m.m, uintptr(unsafe.Pointer(&sx)), uintptr(unsafe.Pointer(&sy)))
|
||||||
|
panicError()
|
||||||
|
return sx, sy
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Monitor) GetPos() (int, int) {
|
func (m *Monitor) GetPos() (int, int) {
|
||||||
var x, y int32
|
var x, y int32
|
||||||
glfwDLL.call("glfwGetMonitorPos", m.m, uintptr(unsafe.Pointer(&x)), uintptr(unsafe.Pointer(&y)))
|
glfwDLL.call("glfwGetMonitorPos", m.m, uintptr(unsafe.Pointer(&x)), uintptr(unsafe.Pointer(&y)))
|
||||||
|
@ -246,6 +246,8 @@ func ensureMonitors() []*monitor {
|
|||||||
// getMonitorFromPosition must be called on the main thread.
|
// getMonitorFromPosition must be called on the main thread.
|
||||||
func getMonitorFromPosition(wx, wy int) *monitor {
|
func getMonitorFromPosition(wx, wy int) *monitor {
|
||||||
for _, m := range ensureMonitors() {
|
for _, m := range ensureMonitors() {
|
||||||
|
// TODO: Fix incorrectness in the cases of https://github.com/glfw/glfw/issues/1961.
|
||||||
|
// See also internal/devicescale/impl_desktop.go for a maybe better way of doing this.
|
||||||
if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height {
|
if m.x <= wx && wx < m.x+m.vm.Width && m.y <= wy && wy < m.y+m.vm.Height {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user