...

Source file src/edge-infra.dev/pkg/sds/display/displaymanager/applier/xorg/xorg_applier.go

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

     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  // Returns a new DisplayApplier which can apply display configuration (represented
    20  // as a DisplayConfig) to the node by making calls to Xrandr, Xset and Xinput.
    21  //
    22  // Must be instantiated with xorg.OutputIDs and xorg.InputIDs to allow the
    23  // applier to identify devices read by the Xorg DisplayReader.
    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  	// outputIDs maps display-ports to output IDs, e.g.
    34  	//   {"card0-HDMI-A-1":"HDMI1","card0-DP-1":"DP1"}
    35  	outputIDs map[v2.DisplayPort]xorg.OutputID
    36  	// inputIDs maps input device names to each of their xinput IDs, e.g.
    37  	//   {"elo_touch_2077":["13","14"],"elo_touch_2088":["16"]}
    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  // Validates each display in the DisplayConfig has an ID.
    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  // Removes mappings from the display config where a matching input device does not exist,
    80  // or it was already mapped to another display.
    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  		// if the requested input device exists, add the mapping and remove it from the remaining devices
    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  		// remove mapping slice if empty
   101  		if len(displayInputDevices) == 0 {
   102  			displayInputDevices = nil
   103  		}
   104  
   105  		display.InputDeviceMappings = displayInputDevices
   106  		displayConfig.Displays[idx] = display
   107  	}
   108  }
   109  
   110  // Removes the first instance of the target input device from inputDevices.
   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  	// create output ID layout
   150  	layout := []xorg.OutputID{}
   151  	for _, dp := range displayConfig.Layout {
   152  		layout = append(layout, outputIDs[dp])
   153  	}
   154  
   155  	// set first display to position 0x0
   156  	cmd.SetOutputPosition(layout[0], 0, 0)
   157  
   158  	// set each remaining to be right-of the previous
   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  	// do nothing if DPMS is not set
   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  	// Set Enabled last as timeouts override it
   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  	// track which device runtime IDs are available to assign to mappings
   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  	// attempt to find an input device ID for each requested mapping,
   218  	// adding the relevant map-to-output command if found
   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  // Attempts to find an unassigned device with the target input device name,
   228  // returning nil if not found or already assigned.
   229  func findAvailableInputDeviceWithName(targetName v2.InputDeviceName, inputIDs map[v2.InputDeviceName][]xorg.InputDeviceID, inputDeviceAvailability map[xorg.InputDeviceID]bool) *xorg.InputDeviceID {
   230  	// iterate over the possible device IDs for the target name
   231  	for _, inputID := range inputIDs[targetName] {
   232  		// if we find an available ID, set as unavailable and return it
   233  		if inputDeviceAvailability[inputID] {
   234  			inputDeviceAvailability[inputID] = false
   235  			return &inputID
   236  		}
   237  	}
   238  
   239  	return nil
   240  }
   241  

View as plain text