ui_unix.go (5740B)
1 // Copyright 2016 Hajime Hoshi 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 //go:build (dragonfly || freebsd || linux || netbsd || openbsd || solaris) && !android 16 // +build dragonfly freebsd linux netbsd openbsd solaris 17 // +build !android 18 19 package glfw 20 21 import ( 22 "fmt" 23 "math" 24 "runtime" 25 26 "github.com/hajimehoshi/ebiten/v2/internal/driver" 27 "github.com/hajimehoshi/ebiten/v2/internal/glfw" 28 "github.com/jezek/xgb" 29 "github.com/jezek/xgb/randr" 30 "github.com/jezek/xgb/xproto" 31 ) 32 33 type videoModeScaleCacheKey struct{ X, Y int } 34 35 var videoModeScaleCache = map[videoModeScaleCacheKey]float64{} 36 37 // clearVideoModeScaleCache must be called from the main thread. 38 func clearVideoModeScaleCache() { 39 for k := range videoModeScaleCache { 40 delete(videoModeScaleCache, k) 41 } 42 } 43 44 // videoModeScale must be called from the main thread. 45 func videoModeScale(m *glfw.Monitor) float64 { 46 // Caching wrapper for videoModeScaleUncached as 47 // videoModeScaleUncached may be expensive (uses blocking calls on X connection) 48 // and public ScreenSizeInFullscreen API needs the videoModeScale. 49 monitorX, monitorY := m.GetPos() 50 cacheKey := videoModeScaleCacheKey{X: monitorX, Y: monitorY} 51 if cached, ok := videoModeScaleCache[cacheKey]; ok { 52 return cached 53 } 54 55 scale := videoModeScaleUncached(m) 56 videoModeScaleCache[cacheKey] = scale 57 return scale 58 } 59 60 // videoModeScaleUncached must be called from the main thread. 61 func videoModeScaleUncached(m *glfw.Monitor) float64 { 62 // TODO: if glfw/glfw#1961 gets fixed, this function may need revising. 63 // In case GLFW decides to switch to returning logical pixels, we can just return 1. 64 65 // Note: GLFW currently returns physical pixel sizes, 66 // but we need to predict the window system-side size of the fullscreen window 67 // for Ebiten's `ScreenSizeInFullscreen` public API. 68 // Also at the moment we need this prior to switching to fullscreen, but that might be replacable. 69 // So this function computes the ratio of physical per logical pixels. 70 xconn, err := xgb.NewConn() 71 if err != nil { 72 // No X11 connection? 73 // Assume we're on pure Wayland then. 74 // GLFW/Wayland shouldn't be having this issue. 75 return 1 76 } 77 defer xconn.Close() 78 79 if err := randr.Init(xconn); err != nil { 80 // No RANDR extension? No problem. 81 return 1 82 } 83 84 root := xproto.Setup(xconn).DefaultScreen(xconn).Root 85 res, err := randr.GetScreenResourcesCurrent(xconn, root).Reply() 86 if err != nil { 87 // Likely means RANDR is not working. No problem. 88 return 1 89 } 90 91 monitorX, monitorY := m.GetPos() 92 93 for _, crtc := range res.Crtcs[:res.NumCrtcs] { 94 info, err := randr.GetCrtcInfo(xconn, crtc, res.ConfigTimestamp).Reply() 95 if err != nil { 96 // This Crtc is bad. Maybe just got disconnected? 97 continue 98 } 99 if info.NumOutputs == 0 { 100 // This Crtc is not connected to any output. 101 // In other words, a disabled monitor. 102 continue 103 } 104 if int(info.X) == monitorX && int(info.Y) == monitorY { 105 xWidth, xHeight := info.Width, info.Height 106 vm := m.GetVideoMode() 107 physWidth, physHeight := vm.Width, vm.Height 108 // Return one scale, even though there may be separate X and Y scales. 109 // Return the _larger_ scale, as this would yield a letterboxed display on mismatch, rather than a cut-off one. 110 scale := math.Max(float64(physWidth)/float64(xWidth), float64(physHeight)/float64(xHeight)) 111 return scale 112 } 113 } 114 115 // Monitor not known to XRandR. Weird. 116 return 1 117 } 118 119 // fromGLFWMonitorPixel must be called from the main thread. 120 func (u *UserInterface) fromGLFWMonitorPixel(x float64, videoModeScale float64) float64 { 121 return x / (videoModeScale * u.deviceScaleFactor()) 122 } 123 124 // fromGLFWPixel must be called from the main thread. 125 func (u *UserInterface) fromGLFWPixel(x float64) float64 { 126 return x / u.deviceScaleFactor() 127 } 128 129 // toGLFWPixel must be called from the main thread. 130 func (u *UserInterface) toGLFWPixel(x float64) float64 { 131 return x * u.deviceScaleFactor() 132 } 133 134 func (u *UserInterface) adjustWindowPosition(x, y int) (int, int) { 135 return x, y 136 } 137 138 func currentMonitorByOS(_ *glfw.Window) *glfw.Monitor { 139 // TODO: Implement this correctly. (#1119). 140 return nil 141 } 142 143 func (u *UserInterface) nativeWindow() uintptr { 144 // TODO: Implement this. 145 return 0 146 } 147 148 func (u *UserInterface) isNativeFullscreen() bool { 149 return false 150 } 151 152 func (u *UserInterface) setNativeCursor(shape driver.CursorShape) { 153 // TODO: Use native API in the future (#1571) 154 u.window.SetCursor(glfwSystemCursors[shape]) 155 } 156 157 func (u *UserInterface) isNativeFullscreenAvailable() bool { 158 return false 159 } 160 161 func (u *UserInterface) setNativeFullscreen(fullscreen bool) { 162 panic(fmt.Sprintf("glfw: setNativeFullscreen is not implemented in this environment: %s", runtime.GOOS)) 163 } 164 165 func (u *UserInterface) adjustViewSize() { 166 } 167 168 func initializeWindowAfterCreation(w *glfw.Window) { 169 // Show the window once before getting the position of the window. 170 // On Linux/Unix, the window position is not reliable before showing. 171 w.Show() 172 173 // Hiding the window makes the position unreliable again. Do not call w.Hide() here (#1829) 174 // Calling Hide is problematic especially on XWayland and/or Sway. 175 // Apparently the window state is inconsistent just after the window is created, but we are not sure. 176 // For more details, see the discussion in #1829. 177 }