1 package xorg
2
3 import (
4 "context"
5 "fmt"
6
7 "slices"
8
9 "edge-infra.dev/pkg/sds/display/displaymanager/applier"
10 "edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/command"
11 "edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/command/runner"
12 "edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/command/xinput"
13 "edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/command/xrandr"
14 "edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/command/xset"
15 v2 "edge-infra.dev/pkg/sds/display/k8s/apis/v2"
16 "edge-infra.dev/pkg/sds/lib/xorg"
17 )
18
19
20
21
22
23
24 func NewXorgDisplayApplier(outputIDs map[v2.DisplayPort]xorg.OutputID, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, r runner.Runner) applier.DisplayApplier {
25 return &displayApplier{
26 outputIDs: outputIDs,
27 inputIDs: inputIDs,
28 cmdRunner: r,
29 }
30 }
31
32 type displayApplier struct {
33
34
35 outputIDs map[v2.DisplayPort]xorg.OutputID
36
37
38 inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID
39
40 cmdRunner runner.Runner
41 }
42
43 func (x *displayApplier) Apply(_ context.Context, displayConfig *v2.DisplayConfig) (*v2.DisplayConfig, error) {
44 if displayConfig == nil {
45 return nil, nil
46 }
47
48 if err := validateOutputIDs(displayConfig, x.outputIDs); err != nil {
49 return nil, err
50 }
51
52 filterInputDeviceMappings(displayConfig, x.inputIDs)
53
54 cmds := []command.Command{
55 x.createXrandrCommand(displayConfig),
56 x.createXsetCommand(displayConfig),
57 x.createXinputCommand(displayConfig),
58 }
59
60 for _, cmd := range cmds {
61 if err := cmd.Run(); err != nil {
62 return nil, fmt.Errorf("failed to run %s: %w", cmd.Path(), err)
63 }
64 }
65
66 return displayConfig, nil
67 }
68
69
70 func validateOutputIDs(displayConfig *v2.DisplayConfig, outputIDs map[v2.DisplayPort]xorg.OutputID) error {
71 for _, dp := range displayConfig.DisplayPorts() {
72 if outputID := outputIDs[dp]; outputID == "" {
73 return fmt.Errorf("display %s does not have an output ID", dp)
74 }
75 }
76 return nil
77 }
78
79
80
81 func filterInputDeviceMappings(displayConfig *v2.DisplayConfig, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID) {
82 remainingInputDevices := []v2.InputDeviceName{}
83 for inputDeviceName := range inputIDs {
84 for range inputIDs[inputDeviceName] {
85 remainingInputDevices = append(remainingInputDevices, inputDeviceName)
86 }
87 }
88
89 for idx, display := range displayConfig.Displays {
90 displayInputDevices := []v2.InputDeviceName{}
91
92
93 for _, inputDevice := range display.InputDeviceMappings {
94 if slices.Contains(remainingInputDevices, inputDevice) {
95 displayInputDevices = append(displayInputDevices, inputDevice)
96 remainingInputDevices = removeInputDevice(inputDevice, remainingInputDevices)
97 }
98 }
99
100
101 if len(displayInputDevices) == 0 {
102 displayInputDevices = nil
103 }
104
105 display.InputDeviceMappings = displayInputDevices
106 displayConfig.Displays[idx] = display
107 }
108 }
109
110
111 func removeInputDevice(targetInputDevice v2.InputDeviceName, inputDevices []v2.InputDeviceName) []v2.InputDeviceName {
112 for idx, inputDevice := range inputDevices {
113 if inputDevice == targetInputDevice {
114 return append(inputDevices[:idx], inputDevices[idx+1:]...)
115 }
116 }
117 return inputDevices
118 }
119
120 func (x *displayApplier) createXrandrCommand(displayConfig *v2.DisplayConfig) command.Command {
121 cmd := xrandr.NewXrandrCommand(x.cmdRunner)
122
123 for _, display := range displayConfig.Displays {
124 addDisplayToXrandrCommand(cmd, display, x.outputIDs[display.DisplayPort])
125 }
126
127 addLayoutToXrandrCommand(cmd, displayConfig, x.outputIDs)
128
129 return cmd
130 }
131
132 func addDisplayToXrandrCommand(cmd xrandr.Command, display v2.Display, outputID xorg.OutputID) {
133 if display.IsPrimary() {
134 cmd.SetOutputAsPrimary(outputID)
135 }
136 if display.Resolution != nil {
137 cmd.SetOutputMode(outputID, display.Resolution.String())
138 }
139 if display.Orientation != nil {
140 cmd.SetOutputRotation(outputID, display.Orientation.String())
141 }
142 }
143
144 func addLayoutToXrandrCommand(cmd xrandr.Command, displayConfig *v2.DisplayConfig, outputIDs map[v2.DisplayPort]xorg.OutputID) {
145 if len(displayConfig.Layout) == 0 {
146 return
147 }
148
149
150 layout := []xorg.OutputID{}
151 for _, dp := range displayConfig.Layout {
152 layout = append(layout, outputIDs[dp])
153 }
154
155
156 cmd.SetOutputPosition(layout[0], 0, 0)
157
158
159 for i := 1; i < len(layout); i++ {
160 outputID := layout[i]
161 relativeTo := layout[i-1]
162 cmd.SetOutputRightOf(outputID, relativeTo)
163 }
164 }
165
166 func (x *displayApplier) createXsetCommand(displayConfig *v2.DisplayConfig) command.Command {
167 cmd := xset.NewXsetCommand(x.cmdRunner)
168
169
170 if displayConfig.DPMS == nil {
171 return cmd
172 }
173
174 if displayConfig.DPMS.BlankTime != nil {
175 cmd.SetBlankTimeSeconds(*displayConfig.DPMS.BlankTime)
176 }
177
178 var standBySeconds, suspendTimeSeconds, offTimeSeconds int
179 if displayConfig.DPMS.StandbyTime != nil {
180 standBySeconds = *displayConfig.DPMS.StandbyTime
181 }
182 if displayConfig.DPMS.SuspendTime != nil {
183 suspendTimeSeconds = *displayConfig.DPMS.SuspendTime
184 }
185 if displayConfig.DPMS.OffTime != nil {
186 offTimeSeconds = *displayConfig.DPMS.OffTime
187 }
188 cmd.SetDPMSSettings(standBySeconds, suspendTimeSeconds, offTimeSeconds)
189
190
191 if displayConfig.DPMS.Enabled != nil {
192 cmd.SetDPMS(*displayConfig.DPMS.Enabled)
193 }
194
195 return cmd
196 }
197
198 func (x *displayApplier) createXinputCommand(displayConfig *v2.DisplayConfig) command.Command {
199 cmd := xinput.NewXinputCommand(x.cmdRunner)
200
201
202 inputDeviceAvailability := map[xorg.InputDeviceID]bool{}
203 for inputDeviceName := range x.inputIDs {
204 for _, inputID := range x.inputIDs[inputDeviceName] {
205 inputDeviceAvailability[inputID] = true
206 }
207 }
208
209 for _, display := range displayConfig.Displays {
210 addDisplayToXinputCommand(cmd, display, x.outputIDs[display.DisplayPort], x.inputIDs, inputDeviceAvailability)
211 }
212
213 return cmd
214 }
215
216 func addDisplayToXinputCommand(cmd xinput.Command, display v2.Display, outputID xorg.OutputID, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, inputDeviceAvailability map[xorg.InputDeviceID]bool) {
217
218
219 for _, inputDeviceMapping := range display.InputDeviceMappings {
220 if inputID := findAvailableInputDeviceWithName(inputDeviceMapping, inputIDs, inputDeviceAvailability); inputID != nil {
221 cmd.MapToOutput(outputID, *inputID)
222 return
223 }
224 }
225 }
226
227
228
229 func findAvailableInputDeviceWithName(targetName v2.InputDeviceName, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, inputDeviceAvailability map[xorg.InputDeviceID]bool) *xorg.InputDeviceID {
230
231 for _, inputID := range inputIDs[targetName] {
232
233 if inputDeviceAvailability[inputID] {
234 inputDeviceAvailability[inputID] = false
235 return &inputID
236 }
237 }
238
239 return nil
240 }
241
View as plain text