...

Source file src/edge-infra.dev/pkg/sds/display/displayinfo/xorg/xorg_displayinfo.go

Documentation: edge-infra.dev/pkg/sds/display/displayinfo/xorg

     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  // Adds any input device mappings which displayctl has applied to the display info output.
   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