cinnamon_unix.go (3413B)
1 // Copyright 2020 The Ebiten Authors 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 // +build dragonfly freebsd linux netbsd openbsd solaris 16 // +build !android 17 18 package devicescale 19 20 import ( 21 "encoding/xml" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strconv" 26 27 "github.com/hajimehoshi/ebiten/v2/internal/glfw" 28 ) 29 30 type xmlBool bool 31 32 func (b *xmlBool) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { 33 var s string 34 if err := d.DecodeElement(&s, &start); err != nil { 35 return err 36 } 37 *b = xmlBool(s == "yes") 38 return nil 39 } 40 41 type cinnamonMonitors struct { 42 XMLName xml.Name `xml:"monitors"` 43 Version string `xml:"version,attr"` 44 Configuration []cinnamonMonitorsConfiguration `xml:"configuration"` 45 } 46 47 type cinnamonMonitorsConfiguration struct { 48 BaseScale float64 `xml:"base_scale"` 49 Output []struct { 50 X int `xml:"x"` 51 Y int `xml:"y"` 52 Width int `xml:"width"` 53 Height int `xml:"height"` 54 Scale float64 `xml:"scale"` 55 Primary xmlBool `xml:"primary"` 56 } `xml:"output"` 57 } 58 59 func (c *cinnamonMonitorsConfiguration) matchesWithGLFWMonitors(monitors []*glfw.Monitor) bool { 60 type area struct { 61 X, Y, Width, Height int 62 } 63 areas := map[area]struct{}{} 64 65 for _, o := range c.Output { 66 if o.Width == 0 || o.Height == 0 { 67 continue 68 } 69 areas[area{ 70 X: o.X, 71 Y: o.Y, 72 Width: o.Width, 73 Height: o.Height, 74 }] = struct{}{} 75 } 76 77 if len(areas) != len(monitors) { 78 return false 79 } 80 81 for _, m := range monitors { 82 x, y := m.GetPos() 83 v := m.GetVideoMode() 84 a := area{ 85 X: x, 86 Y: y, 87 Width: v.Width, 88 Height: v.Height, 89 } 90 if _, ok := areas[a]; !ok { 91 return false 92 } 93 } 94 return true 95 } 96 97 func cinnamonScaleFromXML() (float64, error) { 98 home, err := os.UserHomeDir() 99 if err != nil { 100 return 0, err 101 } 102 f, err := os.Open(filepath.Join(home, ".config", "cinnamon-monitors.xml")) 103 if err != nil { 104 return 0, err 105 } 106 defer f.Close() 107 108 d := xml.NewDecoder(f) 109 110 var monitors cinnamonMonitors 111 if err = d.Decode(&monitors); err != nil { 112 return 0, err 113 } 114 115 for _, c := range monitors.Configuration { 116 if !c.matchesWithGLFWMonitors(glfw.GetMonitors()) { 117 continue 118 } 119 for _, v := range c.Output { 120 // TODO: Get the monitor at the specified position. 121 // TODO: Consider the base scale? 122 if v.Primary && v.Scale != 0.0 { 123 return v.Scale, nil 124 } 125 } 126 } 127 return 0, nil 128 } 129 130 func cinnamonScale() float64 { 131 if s, err := cinnamonScaleFromXML(); err == nil && s > 0 { 132 return s 133 } 134 135 out, err := exec.Command("gsettings", "get", "org.cinnamon.desktop.interface", "scaling-factor").Output() 136 if err != nil { 137 if err == exec.ErrNotFound { 138 return 0 139 } 140 if _, ok := err.(*exec.ExitError); ok { 141 return 0 142 } 143 panic(err) 144 } 145 m := gsettingsRe.FindStringSubmatch(string(out)) 146 s, err := strconv.Atoi(m[1]) 147 if err != nil { 148 return 0 149 } 150 return float64(s) 151 }