...

Source file src/edge-infra.dev/pkg/sds/lib/xorg/xrandr/output.go

Documentation: edge-infra.dev/pkg/sds/lib/xorg/xrandr

     1  package xrandr
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/go-logr/logr"
     8  	"github.com/jezek/xgb"
     9  	"github.com/jezek/xgb/randr"
    10  	"github.com/jezek/xgb/xproto"
    11  
    12  	"edge-infra.dev/pkg/sds/lib/edid"
    13  	"edge-infra.dev/pkg/sds/lib/xorg"
    14  )
    15  
    16  const edidKey = "EDID"
    17  
    18  type Outputs []Output
    19  
    20  func (outputs Outputs) String() string {
    21  	var strs []string
    22  	for _, output := range outputs {
    23  		strs = append(strs, output.String())
    24  	}
    25  	return fmt.Sprintf("[%s]", strings.Join(strs, ", "))
    26  }
    27  
    28  type Output struct {
    29  	Output        *randr.GetOutputInfoReply
    30  	Modes         []Mode
    31  	Crtc          *randr.GetCrtcInfoReply
    32  	PreferredMode *Mode
    33  	CurrentMode   *Mode
    34  	Properties    map[string]*randr.GetOutputPropertyReply
    35  	Primary       bool
    36  }
    37  
    38  func (o *Output) String() string {
    39  	outputID := o.OutputID()
    40  	return outputID.String()
    41  }
    42  
    43  func (o *Output) OutputID() xorg.OutputID {
    44  	return xorg.OutputID(o.Output.Name)
    45  }
    46  
    47  func (o *Output) EDID() (*edid.EDID, error) {
    48  	edidProps, ok := o.Properties[edidKey]
    49  	if !ok || edidProps.Data == nil {
    50  		return nil, fmt.Errorf("output has no EDID")
    51  	}
    52  	return edid.NewFromBytes(edidProps.Data)
    53  }
    54  
    55  type Mode struct {
    56  	Info      *randr.ModeInfo
    57  	Name      string
    58  	Preferred bool
    59  	Current   bool
    60  }
    61  
    62  func (m *Mode) String() string {
    63  	return m.Name
    64  }
    65  
    66  func createOutputForRandrOutput(xConn *xgb.Conn, randrOutput randr.Output, screenResources *randr.GetScreenResourcesCurrentReply, rootWindow *xproto.Window, log logr.Logger) (*Output, error) {
    67  	outputInfo, err := randr.GetOutputInfo(xConn, randrOutput, screenResources.ConfigTimestamp).Reply()
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	if outputInfo.Connection != randr.ConnectionConnected {
    73  		return nil, fmt.Errorf("%s is not connected", outputInfo.Name)
    74  	}
    75  
    76  	crtcInfoReply := &randr.GetCrtcInfoReply{}
    77  	if outputInfo.Crtc != randr.BadBadCrtc {
    78  		crtcInfoReply, err = randr.GetCrtcInfo(xConn, outputInfo.Crtc, outputInfo.Timestamp).Reply()
    79  		if err != nil {
    80  			log.Info("Couldn't get crtc: " + err.Error())
    81  		}
    82  	}
    83  
    84  	primaryOutput, err := randr.GetOutputPrimary(xConn, *rootWindow).Reply()
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	modes := getOutputModesFromInfo(outputInfo, screenResources)
    90  	preferredMode := findPreferredMode(modes)
    91  	currentMode := findCurrentMode(modes)
    92  	outputProperties := getPropertiesFromOutput(xConn, randrOutput)
    93  	primary := primaryOutput.Output == randrOutput
    94  
    95  	return &Output{
    96  		Output:        outputInfo,
    97  		Modes:         modes,
    98  		Crtc:          crtcInfoReply,
    99  		PreferredMode: preferredMode,
   100  		CurrentMode:   currentMode,
   101  		Properties:    outputProperties,
   102  		Primary:       primary,
   103  	}, nil
   104  }
   105  
   106  func getOutputModesFromInfo(outputInfo *randr.GetOutputInfoReply, screenResources *randr.GetScreenResourcesCurrentReply) []Mode {
   107  	modes := []Mode{}
   108  
   109  	for modeIdx, mode := range outputInfo.Modes {
   110  		isPreferredMode := modeIdx < int(outputInfo.NumPreferred)
   111  		isCurrentMode := modeIdx < int(outputInfo.NumModes)
   112  		nameStart := uint16(0)
   113  
   114  		for infoIdx := range screenResources.Modes {
   115  			modeInfo := screenResources.Modes[infoIdx]
   116  			currentModeName := string(screenResources.Names[nameStart:(nameStart + modeInfo.NameLen)])
   117  			if modeInfo.Id == uint32(mode) {
   118  				modes = append(modes, Mode{
   119  					Info:      &modeInfo,
   120  					Name:      currentModeName,
   121  					Preferred: isPreferredMode,
   122  					Current:   isCurrentMode,
   123  				})
   124  			}
   125  			nameStart += modeInfo.NameLen
   126  		}
   127  	}
   128  
   129  	return modes
   130  }
   131  
   132  // Returns the preferred mode for an output, returning the highest
   133  // mode if there is not a preferred.
   134  func findPreferredMode(modes []Mode) *Mode {
   135  	if len(modes) == 0 {
   136  		return nil
   137  	}
   138  
   139  	var preferredMode Mode
   140  	hasPreferred := false
   141  
   142  	highestMode := modes[0]
   143  	for _, mode := range modes {
   144  		if mode.Preferred {
   145  			preferredMode = mode
   146  			hasPreferred = true
   147  		}
   148  		if mode.Info.Width > highestMode.Info.Width {
   149  			highestMode = mode
   150  		}
   151  	}
   152  
   153  	// use highest mode if there is no preferred
   154  	if !hasPreferred {
   155  		return &highestMode
   156  	}
   157  
   158  	return &preferredMode
   159  }
   160  
   161  func findCurrentMode(modes []Mode) *Mode {
   162  	for _, mode := range modes {
   163  		if mode.Current {
   164  			return &mode
   165  		}
   166  	}
   167  
   168  	return nil
   169  }
   170  
   171  func getPropertiesFromOutput(xConn *xgb.Conn, randrOutput randr.Output) map[string]*randr.GetOutputPropertyReply {
   172  	outputProperties := map[string]*randr.GetOutputPropertyReply{}
   173  
   174  	listOutputPropReply, err := randr.ListOutputProperties(xConn, randrOutput).Reply()
   175  	if err != nil {
   176  		return nil
   177  	}
   178  
   179  	for _, atom := range listOutputPropReply.Atoms {
   180  		atomNameReply, err := xproto.GetAtomName(xConn, atom).Reply()
   181  		if err != nil {
   182  			continue
   183  		}
   184  		atomName := atomNameReply.Name
   185  
   186  		properties, err := randr.GetOutputProperty(xConn, randrOutput, atom, xproto.AtomAny, 0, 100, false, false).Reply()
   187  		if err != nil {
   188  			continue
   189  		}
   190  
   191  		outputProperties[atomName] = properties
   192  	}
   193  
   194  	return outputProperties
   195  }
   196  

View as plain text