1 package xorg
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "os"
8
9 "github.com/go-logr/logr"
10 "k8s.io/apimachinery/pkg/api/errors"
11 kruntime "k8s.io/apimachinery/pkg/runtime"
12 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
13 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
14 "k8s.io/client-go/tools/clientcmd"
15 "sigs.k8s.io/controller-runtime/pkg/client"
16
17 "edge-infra.dev/pkg/sds/display/displayinfo"
18 "edge-infra.dev/pkg/sds/display/displaymanager/reader"
19 xorgreader "edge-infra.dev/pkg/sds/display/displaymanager/reader/xorg"
20 v2 "edge-infra.dev/pkg/sds/display/k8s/apis/v2"
21 "edge-infra.dev/pkg/sds/lib/xorg"
22 "edge-infra.dev/pkg/sds/lib/xorg/dpms"
23 "edge-infra.dev/pkg/sds/lib/xorg/xinput"
24 "edge-infra.dev/pkg/sds/lib/xorg/xrandr"
25 )
26
27 const (
28 hostnameEnvvar = "HOSTNAME"
29 xorgDisplayManager = "xorg"
30 )
31
32 var scheme = kruntime.NewScheme()
33
34 func init() {
35 utilruntime.Must(clientgoscheme.AddToScheme(scheme))
36 utilruntime.Must(v2.AddToScheme(scheme))
37 }
38
39 type DisplayInfo struct {
40 displayinfo.DisplayInfo
41 Displays []DisplayDeviceInfo `json:"displays,omitempty"`
42 InputDevices []InputDeviceInfo `json:"inputDevices,omitempty"`
43 }
44
45 type DisplayDeviceInfo struct {
46 DisplayPort v2.DisplayPort `json:"displayPort"`
47 MPID *v2.MPID `json:"mpid,omitempty"`
48 DefaultInputDevicePatterns []string `json:"defaultInputDevicePatterns,omitempty"`
49 }
50
51 type InputDeviceInfo struct {
52 Name v2.InputDeviceName `json:"name"`
53 InputIDs []xorg.InputDeviceID `json:"inputIds"`
54 }
55
56 func Run(ctx context.Context) error {
57 hostname := os.Getenv(hostnameEnvvar)
58 if hostname == "" {
59 return fmt.Errorf("%s envvar is not set", hostnameEnvvar)
60 }
61
62 c, err := createK8sClient()
63 if err != nil {
64 return fmt.Errorf("failed to create K8s client: %w", err)
65 }
66
67 r, inputIDs := createDisplayReader(c)
68
69 displayInfo, err := createDisplayInfo(ctx, hostname, inputIDs, r)
70 if err != nil {
71 return fmt.Errorf("unable to create display info: %w", err)
72 }
73
74 if err := addAppliedMappingsToDisplayInfo(ctx, displayInfo, c); err != nil {
75 return fmt.Errorf("unable to add input device mappings to display info: %w", err)
76 }
77
78 if err := printDisplayInfoJSON(displayInfo); err != nil {
79 return fmt.Errorf("unable to output display info")
80 }
81
82 return nil
83 }
84
85 func createDisplayReader(c client.Client) (reader.DisplayReader, map[v2.InputDeviceName][]xorg.InputDeviceID) {
86 outputIDs := map[v2.DisplayPort]xorg.OutputID{}
87 inputIDs := map[v2.InputDeviceName][]xorg.InputDeviceID{}
88 return xorgreader.NewXorgDisplayReader(
89 outputIDs,
90 inputIDs,
91 xrandr.NewXrandr(),
92 xinput.NewXinput(),
93 dpms.NewDPMS(),
94 c,
95 logr.Discard(),
96 ), inputIDs
97 }
98
99 func createK8sClient() (client.Client, error) {
100 clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
101 clientcmd.NewDefaultClientConfigLoadingRules(),
102 &clientcmd.ConfigOverrides{},
103 )
104
105 restConfig, err := clientConfig.ClientConfig()
106 if err != nil {
107 return nil, err
108 }
109
110 return client.New(restConfig, client.Options{Scheme: scheme})
111 }
112
113 func createDisplayInfo(ctx context.Context, hostname string, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, r reader.DisplayReader) (*DisplayInfo, error) {
114 displayConfig, _, err := r.Read(ctx, reader.WithIgnoreRelativeInputDevices())
115 if err != nil {
116 return nil, fmt.Errorf("failed to read node's DisplayConfig: %w", err)
117 }
118
119 return &DisplayInfo{
120 DisplayInfo: displayinfo.NewDisplayInfo(hostname, xorgDisplayManager, *displayConfig),
121 Displays: createDisplayDeviceInfos(displayConfig),
122 InputDevices: createInputDeviceInfos(inputIDs),
123 }, nil
124 }
125
126 func createDisplayDeviceInfos(displayConfig *v2.DisplayConfig) []DisplayDeviceInfo {
127 defaults := v2.Defaults
128
129 displayInfos := []DisplayDeviceInfo{}
130 for _, display := range displayConfig.Displays {
131 displayInfo := DisplayDeviceInfo{
132 DisplayPort: display.DisplayPort,
133 MPID: display.MPID,
134 }
135
136 for mpid, defaultDisplay := range defaults.KnownDisplays {
137 if display.MPID != nil && *display.MPID == mpid {
138 displayInfo.DefaultInputDevicePatterns = defaultDisplay.InputDevicePatterns
139 }
140 }
141
142 displayInfos = append(displayInfos, displayInfo)
143 }
144
145 return displayInfos
146 }
147
148 func createInputDeviceInfos(inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID) []InputDeviceInfo {
149 inputDeviceInfos := []InputDeviceInfo{}
150 for inputDeviceName := range inputIDs {
151 inputDeviceInfos = append(inputDeviceInfos, InputDeviceInfo{
152 Name: inputDeviceName,
153 InputIDs: inputIDs[inputDeviceName],
154 })
155 }
156 return inputDeviceInfos
157 }
158
159
160 func addAppliedMappingsToDisplayInfo(ctx context.Context, displayInfo *DisplayInfo, c client.Client) error {
161 nodeDisplayConfig := &v2.NodeDisplayConfig{}
162 if err := c.Get(ctx, client.ObjectKey{Name: displayInfo.Hostname}, nodeDisplayConfig); errors.IsNotFound(err) {
163 return nil
164 } else if err != nil {
165 return err
166 }
167
168 appliedDisplayConfig := nodeDisplayConfig.AppliedDisplayConfig()
169 if appliedDisplayConfig == nil {
170 return nil
171 }
172
173 for _, display := range displayInfo.DisplayConfig.Displays {
174 if appliedDisplay := appliedDisplayConfig.Displays.FindByDisplayPort(display.DisplayPort); appliedDisplay != nil {
175 display.InputDeviceMappings = appliedDisplay.InputDeviceMappings
176 displayInfo.DisplayConfig.Displays.UpdateDisplay(display)
177 }
178 }
179
180 return nil
181 }
182
183 func printDisplayInfoJSON(displayInfo *DisplayInfo) error {
184 data, err := json.MarshalIndent(displayInfo, "", " ")
185 if err != nil {
186 return fmt.Errorf("failed to marshal DisplayInfo to JSON: %w", err)
187 }
188
189 fmt.Println(string(data))
190
191 return nil
192 }
193
View as plain text