1 package xrandr
2
3 import (
4 "fmt"
5 "strings"
6
7 "github.com/go-logr/logr"
8 "github.com/jezek/xgb"
9 "github.com/jezek/xgb/randr"
10 "github.com/jezek/xgb/xproto"
11
12 "edge-infra.dev/pkg/sds/lib/edid"
13 "edge-infra.dev/pkg/sds/lib/xorg"
14 )
15
16 const edidKey = "EDID"
17
18 type Outputs []Output
19
20 func (outputs Outputs) String() string {
21 var strs []string
22 for _, output := range outputs {
23 strs = append(strs, output.String())
24 }
25 return fmt.Sprintf("[%s]", strings.Join(strs, ", "))
26 }
27
28 type Output struct {
29 Output *randr.GetOutputInfoReply
30 Modes []Mode
31 Crtc *randr.GetCrtcInfoReply
32 PreferredMode *Mode
33 CurrentMode *Mode
34 Properties map[string]*randr.GetOutputPropertyReply
35 Primary bool
36 }
37
38 func (o *Output) String() string {
39 outputID := o.OutputID()
40 return outputID.String()
41 }
42
43 func (o *Output) OutputID() xorg.OutputID {
44 return xorg.OutputID(o.Output.Name)
45 }
46
47 func (o *Output) EDID() (*edid.EDID, error) {
48 edidProps, ok := o.Properties[edidKey]
49 if !ok || edidProps.Data == nil {
50 return nil, fmt.Errorf("output has no EDID")
51 }
52 return edid.NewFromBytes(edidProps.Data)
53 }
54
55 type Mode struct {
56 Info *randr.ModeInfo
57 Name string
58 Preferred bool
59 Current bool
60 }
61
62 func (m *Mode) String() string {
63 return m.Name
64 }
65
66 func createOutputForRandrOutput(xConn *xgb.Conn, randrOutput randr.Output, screenResources *randr.GetScreenResourcesCurrentReply, rootWindow *xproto.Window, log logr.Logger) (*Output, error) {
67 outputInfo, err := randr.GetOutputInfo(xConn, randrOutput, screenResources.ConfigTimestamp).Reply()
68 if err != nil {
69 return nil, err
70 }
71
72 if outputInfo.Connection != randr.ConnectionConnected {
73 return nil, fmt.Errorf("%s is not connected", outputInfo.Name)
74 }
75
76 crtcInfoReply := &randr.GetCrtcInfoReply{}
77 if outputInfo.Crtc != randr.BadBadCrtc {
78 crtcInfoReply, err = randr.GetCrtcInfo(xConn, outputInfo.Crtc, outputInfo.Timestamp).Reply()
79 if err != nil {
80 log.Info("Couldn't get crtc: " + err.Error())
81 }
82 }
83
84 primaryOutput, err := randr.GetOutputPrimary(xConn, *rootWindow).Reply()
85 if err != nil {
86 return nil, err
87 }
88
89 modes := getOutputModesFromInfo(outputInfo, screenResources)
90 preferredMode := findPreferredMode(modes)
91 currentMode := findCurrentMode(modes)
92 outputProperties := getPropertiesFromOutput(xConn, randrOutput)
93 primary := primaryOutput.Output == randrOutput
94
95 return &Output{
96 Output: outputInfo,
97 Modes: modes,
98 Crtc: crtcInfoReply,
99 PreferredMode: preferredMode,
100 CurrentMode: currentMode,
101 Properties: outputProperties,
102 Primary: primary,
103 }, nil
104 }
105
106 func getOutputModesFromInfo(outputInfo *randr.GetOutputInfoReply, screenResources *randr.GetScreenResourcesCurrentReply) []Mode {
107 modes := []Mode{}
108
109 for modeIdx, mode := range outputInfo.Modes {
110 isPreferredMode := modeIdx < int(outputInfo.NumPreferred)
111 isCurrentMode := modeIdx < int(outputInfo.NumModes)
112 nameStart := uint16(0)
113
114 for infoIdx := range screenResources.Modes {
115 modeInfo := screenResources.Modes[infoIdx]
116 currentModeName := string(screenResources.Names[nameStart:(nameStart + modeInfo.NameLen)])
117 if modeInfo.Id == uint32(mode) {
118 modes = append(modes, Mode{
119 Info: &modeInfo,
120 Name: currentModeName,
121 Preferred: isPreferredMode,
122 Current: isCurrentMode,
123 })
124 }
125 nameStart += modeInfo.NameLen
126 }
127 }
128
129 return modes
130 }
131
132
133
134 func findPreferredMode(modes []Mode) *Mode {
135 if len(modes) == 0 {
136 return nil
137 }
138
139 var preferredMode Mode
140 hasPreferred := false
141
142 highestMode := modes[0]
143 for _, mode := range modes {
144 if mode.Preferred {
145 preferredMode = mode
146 hasPreferred = true
147 }
148 if mode.Info.Width > highestMode.Info.Width {
149 highestMode = mode
150 }
151 }
152
153
154 if !hasPreferred {
155 return &highestMode
156 }
157
158 return &preferredMode
159 }
160
161 func findCurrentMode(modes []Mode) *Mode {
162 for _, mode := range modes {
163 if mode.Current {
164 return &mode
165 }
166 }
167
168 return nil
169 }
170
171 func getPropertiesFromOutput(xConn *xgb.Conn, randrOutput randr.Output) map[string]*randr.GetOutputPropertyReply {
172 outputProperties := map[string]*randr.GetOutputPropertyReply{}
173
174 listOutputPropReply, err := randr.ListOutputProperties(xConn, randrOutput).Reply()
175 if err != nil {
176 return nil
177 }
178
179 for _, atom := range listOutputPropReply.Atoms {
180 atomNameReply, err := xproto.GetAtomName(xConn, atom).Reply()
181 if err != nil {
182 continue
183 }
184 atomName := atomNameReply.Name
185
186 properties, err := randr.GetOutputProperty(xConn, randrOutput, atom, xproto.AtomAny, 0, 100, false, false).Reply()
187 if err != nil {
188 continue
189 }
190
191 outputProperties[atomName] = properties
192 }
193
194 return outputProperties
195 }
196
View as plain text