mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +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/oto/v2 v2.1.0-alpha.0.20210912073017-18657977e3dc
|
||||
github.com/jakecoffman/cp v1.1.0
|
||||
github.com/jezek/xgb v0.0.0-20210312150743-0e0f116e1240 // indirect
|
||||
github.com/jfreymuth/oggvorbis v1.0.3
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||
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/jakecoffman/cp v1.1.0 h1:bhKvCNbAddYegYHSV5abG3G23vZdsISgqXa4X/lK8Oo=
|
||||
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/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
|
||||
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).
|
||||
// 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 {
|
||||
m.Lock()
|
||||
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");
|
||||
// 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
|
||||
// limitations under the License.
|
||||
|
||||
//go:build (darwin && !ios) || windows
|
||||
// +build darwin,!ios windows
|
||||
|
||||
package devicescale
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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) {
|
||||
var x, y int32
|
||||
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.
|
||||
func getMonitorFromPosition(wx, wy int) *monitor {
|
||||
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 {
|
||||
return m
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user