Display

A Display is one monitor as a Kubernetes resource. The operator creates one for every monitor it probes, cluster-scoped like a Node, named by the same monitor id the devices publish as monitor.liken.sh/id. You never create or delete one. The operator writes the whole of status: the controls the panel declares, the values it last saw, and the values it saved before an override. You write the resting fields of spec, and a machine writer, such as a media layer that darkens idle screens, sets and lifts spec.override.

The API server selects Displays by status.node, so a list or a watch can take the monitors of one machine:

kubectl get displays --field-selector status.node=node-1
apiVersion: display.liken.sh/v1alpha1
kind: Display
metadata:
  name: boe-1080-display
spec:
  brightness: 80
status:
  node: node-1
  connector: HDMI-A-2
  capabilities:
    brightness:
      max: 100
    input:
      values: [VGA-1, DVI-1, DVI-2, DP-1, DP-2, HDMI-1, HDMI-2]
    power:
      values: [On, Off, HardOff]
  observed:
    brightness: 80
    power: On
  conditions:
    - type: Connected
      status: "True"
    - type: Responsive
      status: "True"
    - type: CompositorServing
      status: "True"
      reason: Serving

The devices reference describes the other paths to a panel: the claim parameters a Play-style workload states once at prepare, and the control device a standing pod claims for the raw wire. The Display is the declarative path: state what the panel should hold, and the operator keeps it there.

One monitor, the controls it reports, and the settings the operator maintains.

spec

The settings the panel should maintain and a temporary override above them. Every field is optional. The operator writes a declared field when the panel diverges from it and does not write a field that spec leaves out.

Field Type Required Description
brightness integer no The panel’s brightness value. It must not exceed status.capabilities.brightness.max.
contrast integer no The panel’s own contrast number.
sharpness integer no The panel’s own sharpness number.
colorPreset string no One value from status.capabilities.colorPreset.values.
input string no One value from status.capabilities.input.values. This resting declaration forces the panel to show that input. On a shared panel, the operator writes the panel back to this machine within one polling window after each switch away. Declare it only on a panel that should always show this machine.
audioVolume integer no The panel’s own volume number.
audioMute boolean no Whether the panel’s own speakers are muted.
mode string no The resting screen mode, in the 1920x1080@60 form, and one of status.modes. A claim’s mode parameter takes precedence while the claim holds the screen. An edit here waits for the claim to end. Applying the mode restarts the compositor once and ends every Wayland client on this card.
layout string no The Layout this screen shows, by name. If this field is absent, the screen shows every window fullscreen with the newest on top. If the name matches no Layout, the screen shows that same arrangement and reports the name under the LayoutResolved condition. Pattern: ^[a-z0-9]([-a-z0-9.]*[a-z0-9])?$.
override object no A temporary layer above the resting settings. When a writer adds this block, the operator saves the current values and applies the override. When the writer deletes it, the operator restores the declared value or the saved value when spec declares none.

spec.override

A temporary layer above the resting settings. When a writer adds this block, the operator saves the current values and applies the override. When the writer deletes it, the operator restores the declared value or the saved value when spec declares none.

Field Type Required Description
backlight string no Hold the panel dark at brightness zero. The operator also takes the lowercase off, with the same meaning. One of: Off, off.
power string no Hold the panel powered down. The operator also takes the lowercase off, with the same meaning. Some panels stop answering DDC/CI from power off; state this only for a panel a drill proved wakes. One of: Off, off.

status

What the operator read and what it last wrote. The operator owns every field here.

Field Type Required Description
node string no The machine whose graphics card drives this panel.
connector string no The connector on that card, in the kernel’s own spelling.
manufacturer string no The monitor’s manufacturer, from its EDID, the same value the device attribute carries.
model string no The monitor’s model name, from its EDID.
serial string no The monitor’s serial, from its EDID, and absent when the monitor states none.
widthMillimeters integer no The panel’s physical width, as the monitor states it.
heightMillimeters integer no The panel’s physical height, as the monitor states it.
physicalAddress string no The HDMI-CEC physical address of the port this machine’s cable is in, in the dotted form 1.2.0.0, from the HDMI vendor block of the EDID the connector serves. A CEC adapter announces this address when it speaks for this machine. The field keeps the last valid address while the connector serves no EDID for this monitor, while it serves no valid address in it, or while two connectors on this node serve this monitor with different addresses, and the PhysicalAddressCurrent condition says which. The field is absent while the monitor has never served a valid address, for example on a DisplayPort cable. 0.0.0.0, f.f.f.f, and an address with a non-zero digit after a zero are not valid.
mode object no The mode this output runs, from the two parties that each report one. The kernel syncing a mode on the connector and the compositor serving canvases at that mode are two different facts, and a client draws at the second one, so a gap between the two values is the canvas defect and this object is where it shows.
modes []string no Every mode the card offers for this connector, whole, where the device attribute of the same name is cut to fit the API’s limit on an attribute value. This is the list spec.mode is judged against. It is absent while the operator holds no connection to a compositor.
capabilities map[string]object no The controls the panel declares, of the MCCS common core. A control with a value list takes those values, and a control with a maximum takes a number up to it.
observed object no The last value the operator read or wrote for each control. It reads the panel during probing, before an override capture, while it actuates a control, and about every ten seconds when the panel is lit and has no override. The ten-second read finds changes made with the panel’s own buttons. The operator never reads a panel in standby or off because a DDC read wakes some panels.
captured object no The values the operator saved before it applied an override. The save commits before the panel goes dark, so the restore value survives an operator restart.
unconfirmed []object no Each write that the device did not confirm, for the current metadata.generation. A panel can read back a value other than the value the operator wrote, a restore can run out of attempts, the compositor can serve a mode other than the mode spec.mode states, and a panel can change a declared value by itself after the operator wrote it back three times. The operator makes such a write once and records it here. It does not make the write again, after a restart too, until spec changes. The record goes when the device holds the value. A restore that stops here keeps status.captured.
written []object no How many times the operator wrote each declared value in the current metadata.generation, with the panel confirming each write. The first write sets the value, and each later write puts it back after the panel moved away from it. The operator writes a value back at most three times in one generation, and then records it in status.unconfirmed.
surfaces []object no Every window the compositor holds on this screen, in arrival order, whether or not a region shows it. A window with no region is running but is not displayed. Read this field first when a program draws nothing visible. An ID lasts only for the compositor that assigned it. A compositor restart ends every window, and programs reconnect under new IDs.
layout object no The arrangement the screen is drawn to, and what each region shows.
conditions []object no Connected reports the panel on its connector. status.surfaces and status.layout are empty while it is False, because the compositor shows no window on a connector with no panel. Responsive reports the panel answering DDC/CI, with the reason NoDDCReply when it does not. LayoutResolved is False with the reason LayoutNotFound while spec.layout names a Layout the cluster does not hold, and the screen shows the default arrangement until it does. CompositorServing reports the compositor behind the screen. It is False with the reason Down while the compositor’s socket refuses the connect, and with the reason Hung while the socket accepts and the compositor answers nothing; the message is the socket’s own words. status.surfaces and status.layout are empty for as long as it is False. PhysicalAddressCurrent is True with the reason ReadFromEDID while the connector’s current EDID serves status.physicalAddress. It is False with the reason Retained while the connector serves no EDID for this monitor or serves no valid address, and status.physicalAddress then holds the last valid address; the message names the time the connector stopped serving it. It is False with the reason Ambiguous while two connected connectors on this node serve this monitor with different addresses; the message names both connectors and both addresses, and status.physicalAddress keeps the value it held before they disagreed. The condition is absent while the monitor has never served a valid address.

status.mode

The mode this output runs, from the two parties that each report one. The kernel syncing a mode on the connector and the compositor serving canvases at that mode are two different facts, and a client draws at the second one, so a gap between the two values is the canvas defect and this object is where it shows.

Field Type Required Description
kernel string no The mode the card reports this connector is synced to, absent while it drives nothing and while the operator holds no connection to a compositor.
weston string no The mode the compositor reports it serves canvases at, from its own wl_output events, absent while the operator holds no connection to a compositor.

status.capabilities.*

The controls the panel declares, of the MCCS common core. A control with a value list takes those values, and a control with a maximum takes a number up to it.

Field Type Required Description
max integer no The largest number the panel accepts for a continuous control.
values []string no Every value the panel accepts for a non-continuous control. The power control lists its values under the names status.observed.power uses.

status.observed

The last value the operator read or wrote for each control. It reads the panel during probing, before an override capture, while it actuates a control, and about every ten seconds when the panel is lit and has no override. The ten-second read finds changes made with the panel’s own buttons. The operator never reads a panel in standby or off because a DDC read wakes some panels.

Field Type Required Description
brightness integer no
contrast integer no
sharpness integer no
colorPreset string no
input string no
audioVolume integer no
audioMute boolean no
power string no The panel’s power state: On, Standby, Suspend, Off, or HardOff. A value the MCCS table does not name is a hexadecimal number, such as 0x06. The operator reads a power name in any case, so a capture saved in lowercase still restores the panel.

status.unconfirmed[]

Each write that the device did not confirm, for the current metadata.generation. A panel can read back a value other than the value the operator wrote, a restore can run out of attempts, the compositor can serve a mode other than the mode spec.mode states, and a panel can change a declared value by itself after the operator wrote it back three times. The operator makes such a write once and records it here. It does not make the write again, after a restart too, until spec changes. The record goes when the device holds the value. A restore that stops here keeps status.captured.

Field Type Required Description
control string yes The control, in the spelling of status.capabilities, or mode for spec.mode.
value string yes The value the operator wrote.
readback string no The value the device held after the write. Absent when the device did not answer.
generation integer yes The metadata.generation the write was made for.
message string no The failure, in the words of the party that reported it.
time string yes When the operator recorded the write.

status.written[]

How many times the operator wrote each declared value in the current metadata.generation, with the panel confirming each write. The first write sets the value, and each later write puts it back after the panel moved away from it. The operator writes a value back at most three times in one generation, and then records it in status.unconfirmed.

Field Type Required Description
control string yes The control, in the spelling of status.capabilities.
value string yes The declared value.
generation integer yes The metadata.generation the writes were made for.
count integer yes The number of confirmed writes.

status.surfaces[]

Every window the compositor holds on this screen, in arrival order, whether or not a region shows it. A window with no region is running but is not displayed. Read this field first when a program draws nothing visible. An ID lasts only for the compositor that assigned it. A compositor restart ends every window, and programs reconnect under new IDs.

Field Type Required Description
id string yes The window’s id, which the compositor assigned. status.layout names it beside the region that shows it.
claim string no The ResourceClaim whose socket the window arrived on, as namespace/name. Empty for a window on the shared socket, which belongs to no claim.
pods []string no The pods that hold the claim, each as namespace/name.
labels map[string]string no The labels every holder of the claim carries with the same value. These are the labels a region’s selector reads. When one pod holds the claim they are that pod’s labels.
size object no The size the program last drew, in pixels. A window in a region draws at the region’s size, so a size that does not match its region is a program that ignored the compositor’s request and is scaled to fit.
region string no The region that shows the window. Empty for a window no region took, which is not on the screen.

status.surfaces[].size

The size the program last drew, in pixels. A window in a region draws at the region’s size, so a size that does not match its region is a program that ignored the compositor’s request and is scaled to fit.

Field Type Required Description
width integer no The width in pixels.
height integer no The height in pixels.

status.layout

The arrangement the screen is drawn to, and what each region shows.

Field Type Required Description
name string no The Layout in force, or default for a screen that names none or names one that does not exist.
regions []object no Each region in stacking order, with the window on top of it.

status.layout.regions[]

Each region in stacking order, with the window on top of it.

Field Type Required Description
name string yes The region’s name, as the Layout states it.
surface string no The id of the window on top of the region, the newest of the claim it shows, or the word empty when no window matched it.

status.conditions[]

Connected reports the panel on its connector. status.surfaces and status.layout are empty while it is False, because the compositor shows no window on a connector with no panel. Responsive reports the panel answering DDC/CI, with the reason NoDDCReply when it does not. LayoutResolved is False with the reason LayoutNotFound while spec.layout names a Layout the cluster does not hold, and the screen shows the default arrangement until it does. CompositorServing reports the compositor behind the screen. It is False with the reason Down while the compositor’s socket refuses the connect, and with the reason Hung while the socket accepts and the compositor answers nothing; the message is the socket’s own words. status.surfaces and status.layout are empty for as long as it is False. PhysicalAddressCurrent is True with the reason ReadFromEDID while the connector’s current EDID serves status.physicalAddress. It is False with the reason Retained while the connector serves no EDID for this monitor or serves no valid address, and status.physicalAddress then holds the last valid address; the message names the time the connector stopped serving it. It is False with the reason Ambiguous while two connected connectors on this node serve this monitor with different addresses; the message names both connectors and both addresses, and status.physicalAddress keeps the value it held before they disagreed. The condition is absent while the monitor has never served a valid address.

Field Type Required Description
type string yes
status string yes One of: True, False, Unknown.
reason string yes
message string no
lastTransitionTime string yes

The resting layer

A declared field is a standing instruction. On every pass the operator compares the declaration with the value it last saw, and it writes the panel only where the two diverge, so a settled panel costs nothing on the wire. A declared value is validated against status.capabilities: a value the panel does not carry fails the pass and is never written. An empty spec writes nothing at all. The operator invents no value, ever: a panel with no declarations keeps whatever its own menu holds.

A panel can take a write and read back another value. For example, a panel can report its input under a code other than the one the operator wrote. The operator writes such a value once for each metadata.generation, records it in status.unconfirmed with the value the panel read back, and does not write it again until spec changes. The record is in status, so a restarted operator does not write it again either.

A panel can also take the write, confirm it, and later change the value by itself. For example, a panel can switch to the input that carries a signal. The operator writes a declared value back at most three times in one generation after the first write, and counts the writes in status.written. After the third write back, it records the value in status.unconfirmed with a message that says the panel keeps changing it, and it writes the value again only after spec changes. A person who changes a value at the panel’s own buttons uses the same count.

Each write to a panel prints one line in the operator’s log, with the value before and the value after.

The override

spec.override holds a temporary state above the resting layer, the way kubectl cordon holds spec.unschedulable above a Node’s definition. A writer adds the block, and the operator obeys it. The writer deletes the block, and the operator restores the panel: to the resting declaration where spec states one, otherwise to the value it captured.

The capture is the load-bearing step. Before the operator obeys backlight: Off, it reads the panel’s brightness and writes the value to status.captured, and only a committed capture is followed by the write that darkens the panel. A capture in etcd survives an operator restart, a pod move, and a reboot, so the restore does too. The restore retries until the panel reads back the value, because a panel that is waking answers late. It makes at most eight writes of each control, over about 90 seconds. A restore that runs out of writes keeps status.captured, and status.unconfirmed names the control. An edit to spec starts the restore again.

An override has no timeout. If the writer that set one crashes, the panel stays dark until the writer returns or a person deletes the block. That failure is visible: kubectl get display shows the standing override, and the block’s field manager names the writer that owes the lift.

The resting mode

spec.mode follows the resting pattern with one difference in weight: a mode lands through the compositor, and applying it restarts the compositor once, which ends every Wayland client on the card. So the operator applies a resting mode only while no claim holds the screen. A claim’s own mode parameter wins for the claim’s lifetime, a spec.mode edit during a claim waits for the claim to end, and the unprepare that frees the screen restores the declaration promptly. A claim holds the screen while a pod that is not being deleted holds the claim, before its prepare succeeds too. The kubelet retries a prepare that failed, and each prepare switches the screen to the claim’s mode, so a resting mode applied between two retries would restart the compositor each way.

A new operator pod starts the compositor at each monitor’s resting mode, so the screen takes one modeset, not a modeset to the preferred mode and a second one to spec.mode. When the compositor serves another mode after its restart, the operator records the mode in status.unconfirmed and does not restart the compositor for it again until spec changes. A compositor that crashed waits in the kubelet’s crash backoff and has served no mode yet, so the operator waits for it to start, up to six minutes, and gives it 10 seconds from then.

The two values of the mode

status.mode reports the mode twice, because two parties each report one and they can disagree. kernel is the mode the graphics card is synced to on the connector. weston is the mode the compositor lays canvases out at, read from the compositor’s own wl_output events over a standing connection the operator holds. A client draws at the second one. When the two values differ, the clients on that screen are drawn at the wrong size, and the operator restarts the compositor to correct it once the screens are free. weston is absent while the operator holds no connection to a compositor. kernel is absent while the connector drives nothing, and also while the operator holds no connection to a compositor, because the operator opens the card only while it holds a connection to a compositor. kubectl get displays shows the two as the MODE and CANVAS columns.

The physical address

status.physicalAddress is the HDMI-CEC physical address of the port this machine’s cable is in: four hex digits that give the path from the TV, one digit for each HDMI port on the way. A machine on input 2 of a receiver that is on the TV’s input 1 reads 1.2.0.0. An HDMI sink serves each of its ports an EDID whose vendor block states that port’s address, and the operator reads it from the EDID on the connector. A CEC adapter on the machine has no EDID of its own, so it announces this address when it sends Active Source, and the receiver and the TV switch to the machine’s picture. The operator does not open a CEC adapter or send a CEC message.

A receiver in standby can stop serving its EDID, or pass the TV’s EDID through, which belongs to another Display. The machine’s port has not moved, so the field keeps the last valid address, and the PhysicalAddressCurrent condition states where the value came from:

Status Reason Meaning
True ReadFromEDID The connector’s current EDID serves this address.
False Retained The connector serves no EDID for this monitor, or serves no valid address in it. The message names the time it stopped serving the address.
False Ambiguous Two connected connectors serve this monitor with different addresses. The message names both connectors and both addresses.

The condition is absent while the monitor has never served a valid address, for example on a DisplayPort cable. 0.0.0.0 is the TV’s own address, which a sink serves when it states none, and f.f.f.f is the invalid address, so the operator publishes neither. It also refuses an address with a non-zero digit after a zero, such as 1.0.2.0, because a zero ends the path. The output device publishes the same value as its physicalAddress attribute, from the current EDID only, so a dark connector publishes none. Read the retained value from the Display.

The address belongs to one connector, and a Display has one connector. Two connectors on one machine that serve EDIDs with the same identity, such as two cables to one receiver, map to one Display, and it reports the connector that sorts last by name among those that are connected.

Those two connectors can serve different addresses, for example one cable that carries the picture into a receiver input and a second, through a CEC adapter, into another input of the same receiver. When they disagree, the Display publishes no current address: PhysicalAddressCurrent reads Ambiguous, neither connector’s device states a physicalAddress attribute, and status.physicalAddress keeps the value it held before the two disagreed.

Shared screens

A monitor with several inputs dims all of them at once, because brightness and power are panel-global, and the operator writes what an override states whenever the panel answers. Panels do not say reliably which input they show: the query is optional, and a panel can answer it with the name of the port the question arrived on. So whether a screen should ever go dark is its owner’s declaration, not the operator’s guess. State it in the layer that writes the override; the media operator’s Player carries an idle policy whose offAfterSeconds: 0 keeps a shared screen’s panel untouched.

Observed values

status.observed is what the operator last read or wrote. The operator touches the wire when it probes, when it captures before an override, when it actuates, and about every ten seconds for a panel that is lit and under no override. That last read is what finds a change a person made at the panel’s own menu, and it is what makes a resting declaration hold: the pass that finds the divergence writes the declaration back, up to three times in one generation. A panel in standby or off, a panel an override holds, and a panel that answers nothing are never read on a timer, because a DDC/CI read is itself a wake stimulus on some panels, and a polling loop would relight the screens the override layer darkened. For those panels, observed stays what the operator last saw.

One writer per wire

The operator is the one process that writes a panel’s i2c wire for the Display: resting declarations, overrides, and restores all land through the same reconciler. The one-writer rule in the devices reference still governs the other paths: a pod that holds a connector’s control device owns that wire while it runs, so do not declare resting values or write overrides for a screen whose control device a pod holds.

Events

The operator posts an Event on a Display for each condition that changes and for each action it takes on the screen. A condition states what is true now. An Event states what happened, and the API server deletes it one hour after its last write. A Display is cluster-scoped, so its Events are in the default namespace. kubectl describe display shows them. kubectl events shows them only with -n default or -A:

kubectl events -n default --for display/boe-1080-display

A condition that changes posts one Event with the condition’s own reason and message. A new message with the same status and reason posts nothing. The operator posts the Event after the status write lands, so a write the API server refuses posts nothing. The same Event again within 10 minutes adds to the count of the first one, and kubectl describe shows it once, with the count.

Reason Type Condition or action
PanelAttached Normal Connected is True: a panel is on the connector.
NoPanel Normal Connected is False: the connector serves no EDID for this monitor.
AnswersDDC Normal Responsive is True.
NoDDCReply Normal Responsive is False. A panel in standby does not answer DDC/CI.
Serving Normal CompositorServing is True.
Down Warning CompositorServing is False: the compositor’s socket refuses the connect.
Hung Warning CompositorServing is False: the socket accepts and the compositor answers nothing.
ReadFromEDID Normal PhysicalAddressCurrent is True.
Retained Normal PhysicalAddressCurrent is False, and the field keeps the last valid address.
Ambiguous Warning PhysicalAddressCurrent is False: two connectors serve this monitor with different addresses.
LayoutFound, DefaultLayout Normal LayoutResolved is True.
LayoutNotFound Warning LayoutResolved is False: no Layout has the name spec.layout states.
WriteUnconfirmed Warning A write is new in status.unconfirmed. The message names the control, the value, and what the device read back.
ModeChanged Normal A mode change restarts the compositor. The message names the mode before and after. Every screen on the card blanks.
CompositorKilled Warning The compositor answered nothing for 10 seconds, and the operator ended it. The compositor’s container starts it again. The Event is on every Display of the node.
PanelStandbyFailed Warning The panel did not take the standby after its last claim ended, and stays on. The operator does not try again.
Captured Normal A capture through the API returned bytes. The message names the subject, the aspect, and the media type.