package v2 import ( "fmt" "edge-infra.dev/pkg/lib/kernel/drm" v1 "edge-infra.dev/pkg/sds/display/k8s/apis/v1" "edge-infra.dev/pkg/sds/lib/set" ) const ( // A display's card where the card could not be found. UnknownCard = "unknown" // A display which was disconnected during the V1 to V2 upgrade. DisconnectedPort = "disconnected" ) // DisplayConfig defines the configuration for displays. type DisplayConfig struct { // Maps monitor display-ports to display configurations. Displays Displays `json:"displays,omitempty"` // DPMS configuration DPMS *DPMS `json:"dpms,omitempty"` // Layout of the displays as slice, ordered left to right. Layout Layout `json:"layout,omitempty"` // The V1 spec following conversion from V1 to V2 by the // conversion webhook. This will be used by displayctl to // upgrade the spec to V2. // // This must only be set during conversion from V1 to V2 // and cannot be set after the upgrade is complete. V1 *v1.DisplayConfig `json:"v1-displayConfig,omitempty"` } // The set of display-ports from both the displays and layout fields. func (displayConfig *DisplayConfig) DisplayPorts() []DisplayPort { if displayConfig == nil { return []DisplayPort{} } dps := set.OrderedFromSlice(displayConfig.Displays.DisplayPorts()) dps.Add(displayConfig.Layout...) return dps.ToSlice() } // Whether there is V1 spec which is yet to be upgraded. func (displayConfig *DisplayConfig) HasV1() bool { return displayConfig != nil && displayConfig.V1 != nil } // Whether any of the displays were disconnected during V1 to V2 // upgrade and still requrie display-port identification. func (displayConfig *DisplayConfig) HasDisconnected() bool { if displayConfig == nil { return false } for _, display := range displayConfig.Displays { if !display.IsConnected() { return true } } for _, dp := range displayConfig.Layout { if !dp.IsConnected() { return true } } return false } type Displays []Display // Updates the display with matching display-port. // Adds a new display if it is not yet present. func (displays *Displays) UpdateDisplay(display Display) { if display.DisplayPort == "" { return } if currentDisplay := displays.FindByDisplayPort(display.DisplayPort); currentDisplay != nil { *currentDisplay = display } else { *displays = append(*displays, display) } } // The display with given display-port (nil if not found). func (displays Displays) FindByDisplayPort(dp DisplayPort) *Display { for idx, display := range displays { if display.DisplayPort == dp { return &displays[idx] } } return nil } // The display with given MPID (nil if not found). func (displays Displays) FindByMPID(mpid MPID) *Display { for idx, display := range displays { if display.MPID != nil && *display.MPID == mpid { return &displays[idx] } } return nil } // The primary display. func (displays Displays) FindPrimary() *Display { for _, display := range displays { if display.IsPrimary() { return &display } } return nil } // Returns the displays display-ports. func (displays Displays) DisplayPorts() []DisplayPort { dps := []DisplayPort{} for _, display := range displays { dps = append(dps, display.DisplayPort) } return dps } // Display configures a display's monitor and input devices. type Display struct { // DisplayPort is the display card and physical port the // display is connected to, e.g. card0-DP1, card1-HDMI2 etc. // // This field is NOT optional. DisplayPort `json:"displayPort"` // Manufacturer and Product-Code ID, a unique identifier // for the display, i.e. "-". // // It is NOT valid to configure this field in the // NodeDisplayConfig spec. *MPID `json:"mpid,omitempty"` // Primary indicates whether this is the primary display. *Primary `json:"primary,omitempty"` // Orientation of the display, e.g "left" or "inverted". Orientation *Orientation `json:"orientation,omitempty"` // Resolution is the current resolution of the display. *Resolution `json:"resolution,omitempty"` // SupportedResolutions are the valid resolutions the // display can be configured to use. // // It is NOT valid to configure this field in the // NodeDisplayConfig spec. The resolution of the display // can be configured via the Resolution field. SupportedResolutions []Resolution `json:"supportedResolutions,omitempty"` // InputDeviceMappings are the devices the display maps to. InputDeviceMappings []InputDeviceName `json:"inputDeviceMappings,omitempty"` } // +kubebuilder:validation:Pattern=`^(unknown|card\d)(-[A-Za-z0-9]+)+$` // // DisplayPort is the display card and physical port the // display is connected to, e.g. card0-DP1, card1-HDMI-A-2 etc. // // If a card cannot be found for a display it will be marked // unknown, e.g. unknown-DP2. // // Displays that were disconnected during upgrade from V1 to V2 // will be marked disconnected, e.g. unknown-disconnected-1. type DisplayPort string func NewDisplayPort(card, port string) DisplayPort { return DisplayPort(fmt.Sprintf("%s-%s", card, port)) } func (dp *DisplayPort) String() string { if dp == nil { return "" } return string(*dp) } // Whether a display is connected. // // A display can be disconnected if it was not // present during the V1 to V2 upgrade. func (dp DisplayPort) IsConnected() bool { // if the display-port is not a valide connector name, it must be disconncted. err := drm.ValidateConnectorName(dp.String()) return err == nil } // +kubebuilder:validation:Pattern=`^[A-Z]{3}-\d{1,5}$` // // Manufacturer and Product-Code ID, a unique identifier for // the display, i.e. "-". type MPID string func NewMPID(manufacturer string, productCode uint) MPID { return MPID(fmt.Sprintf("%s-%d", manufacturer, productCode)) } func (mpid *MPID) String() string { if mpid == nil { return "" } return string(*mpid) } // Primary indicates whether a display is the primary display. type Primary bool func (display *Display) IsPrimary() bool { if display.Primary == nil { return false } return bool(*display.Primary) } func (display *Display) SetPrimary(primary bool) { display.Primary = (*Primary)(&primary) } // +kubebuilder:validation:Enum=normal;left;right;inverted // // Orientation is the displays orientation, e.g "normal", // "left", "right" or "inverted". type Orientation string var ( NormalOrientation Orientation = "normal" LeftOrientation Orientation = "left" RightOrientation Orientation = "right" InvertedOrientation Orientation = "inverted" ) func (o *Orientation) String() string { if o == nil { return "" } return string(*o) } // Resolution defines the resolution of a display. type Resolution struct { // +kubebuilder:validation:Minimum=0 // // Width of the display in pixels. Width int `json:"width"` // +kubebuilder:validation:Minimum=0 // // Height of the display in pixels. Height int `json:"height"` } func (res *Resolution) String() string { if res == nil { return "" } return fmt.Sprintf("%dx%d", res.Width, res.Height) } // InputDeviceName is the name of an input device type InputDeviceName string func (name *InputDeviceName) String() string { if name == nil { return "" } return string(*name) } // DPMS describes the DPMS configuration for a display. type DPMS struct { // Enabled indicates whether DPMS is enabled for the node. Enabled *bool `json:"enabled,omitempty"` // +kubebuilder:validation:Minimum=0 // // BlankTime configures the number of seconds of inactivity // before reaching the blank phase of the screen saver. BlankTime *int `json:"blankTime,omitempty"` // +kubebuilder:validation:Minimum=0 // // StandbyTime configures the number of seconds of inactivity // before reaching the standby phase of the DPMS mode. StandbyTime *int `json:"standybyTime,omitempty"` // +kubebuilder:validation:Minimum=0 // // SuspendTime configures the number of seconds of inactivity // before reaching the suspend phase of the DPMS mode. SuspendTime *int `json:"suspendTime,omitempty"` // +kubebuilder:validation:Minimum=0 // // OffTime configures the number of seconds of inactivity // before reaching the off phase of the DPMS mode. OffTime *int `json:"offTime,omitempty"` } // Layout describes the physical position of displays as their // display-port's ordered left to right. type Layout []DisplayPort