package xorg import ( "context" "encoding/json" "fmt" "os" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" kruntime "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/controller-runtime/pkg/client" "edge-infra.dev/pkg/sds/display/displayinfo" "edge-infra.dev/pkg/sds/display/displaymanager/reader" xorgreader "edge-infra.dev/pkg/sds/display/displaymanager/reader/xorg" v2 "edge-infra.dev/pkg/sds/display/k8s/apis/v2" "edge-infra.dev/pkg/sds/lib/xorg" "edge-infra.dev/pkg/sds/lib/xorg/dpms" "edge-infra.dev/pkg/sds/lib/xorg/xinput" "edge-infra.dev/pkg/sds/lib/xorg/xrandr" ) const ( hostnameEnvvar = "HOSTNAME" xorgDisplayManager = "xorg" ) var scheme = kruntime.NewScheme() func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v2.AddToScheme(scheme)) } type DisplayInfo struct { displayinfo.DisplayInfo Displays []DisplayDeviceInfo `json:"displays,omitempty"` InputDevices []InputDeviceInfo `json:"inputDevices,omitempty"` } type DisplayDeviceInfo struct { DisplayPort v2.DisplayPort `json:"displayPort"` MPID *v2.MPID `json:"mpid,omitempty"` DefaultInputDevicePatterns []string `json:"defaultInputDevicePatterns,omitempty"` } type InputDeviceInfo struct { Name v2.InputDeviceName `json:"name"` InputIDs []xorg.InputDeviceID `json:"inputIds"` } func Run(ctx context.Context) error { hostname := os.Getenv(hostnameEnvvar) if hostname == "" { return fmt.Errorf("%s envvar is not set", hostnameEnvvar) } c, err := createK8sClient() if err != nil { return fmt.Errorf("failed to create K8s client: %w", err) } r, inputIDs := createDisplayReader(c) displayInfo, err := createDisplayInfo(ctx, hostname, inputIDs, r) if err != nil { return fmt.Errorf("unable to create display info: %w", err) } if err := addAppliedMappingsToDisplayInfo(ctx, displayInfo, c); err != nil { return fmt.Errorf("unable to add input device mappings to display info: %w", err) } if err := printDisplayInfoJSON(displayInfo); err != nil { return fmt.Errorf("unable to output display info") } return nil } func createDisplayReader(c client.Client) (reader.DisplayReader, map[v2.InputDeviceName][]xorg.InputDeviceID) { outputIDs := map[v2.DisplayPort]xorg.OutputID{} inputIDs := map[v2.InputDeviceName][]xorg.InputDeviceID{} return xorgreader.NewXorgDisplayReader( outputIDs, inputIDs, xrandr.NewXrandr(), xinput.NewXinput(), dpms.NewDPMS(), c, logr.Discard(), ), inputIDs } func createK8sClient() (client.Client, error) { clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( clientcmd.NewDefaultClientConfigLoadingRules(), &clientcmd.ConfigOverrides{}, ) restConfig, err := clientConfig.ClientConfig() if err != nil { return nil, err } return client.New(restConfig, client.Options{Scheme: scheme}) } func createDisplayInfo(ctx context.Context, hostname string, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, r reader.DisplayReader) (*DisplayInfo, error) { displayConfig, _, err := r.Read(ctx, reader.WithIgnoreRelativeInputDevices()) if err != nil { return nil, fmt.Errorf("failed to read node's DisplayConfig: %w", err) } return &DisplayInfo{ DisplayInfo: displayinfo.NewDisplayInfo(hostname, xorgDisplayManager, *displayConfig), Displays: createDisplayDeviceInfos(displayConfig), InputDevices: createInputDeviceInfos(inputIDs), }, nil } func createDisplayDeviceInfos(displayConfig *v2.DisplayConfig) []DisplayDeviceInfo { defaults := v2.Defaults displayInfos := []DisplayDeviceInfo{} for _, display := range displayConfig.Displays { displayInfo := DisplayDeviceInfo{ DisplayPort: display.DisplayPort, MPID: display.MPID, } for mpid, defaultDisplay := range defaults.KnownDisplays { if display.MPID != nil && *display.MPID == mpid { displayInfo.DefaultInputDevicePatterns = defaultDisplay.InputDevicePatterns } } displayInfos = append(displayInfos, displayInfo) } return displayInfos } func createInputDeviceInfos(inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID) []InputDeviceInfo { inputDeviceInfos := []InputDeviceInfo{} for inputDeviceName := range inputIDs { inputDeviceInfos = append(inputDeviceInfos, InputDeviceInfo{ Name: inputDeviceName, InputIDs: inputIDs[inputDeviceName], }) } return inputDeviceInfos } // Adds any input device mappings which displayctl has applied to the display info output. func addAppliedMappingsToDisplayInfo(ctx context.Context, displayInfo *DisplayInfo, c client.Client) error { nodeDisplayConfig := &v2.NodeDisplayConfig{} if err := c.Get(ctx, client.ObjectKey{Name: displayInfo.Hostname}, nodeDisplayConfig); errors.IsNotFound(err) { return nil } else if err != nil { return err } appliedDisplayConfig := nodeDisplayConfig.AppliedDisplayConfig() if appliedDisplayConfig == nil { return nil } for _, display := range displayInfo.DisplayConfig.Displays { if appliedDisplay := appliedDisplayConfig.Displays.FindByDisplayPort(display.DisplayPort); appliedDisplay != nil { display.InputDeviceMappings = appliedDisplay.InputDeviceMappings displayInfo.DisplayConfig.Displays.UpdateDisplay(display) } } return nil } func printDisplayInfoJSON(displayInfo *DisplayInfo) error { data, err := json.MarshalIndent(displayInfo, "", " ") if err != nil { return fmt.Errorf("failed to marshal DisplayInfo to JSON: %w", err) } fmt.Println(string(data)) return nil }