
<!-- Generated from deploy/displays.yaml by crdref. Do not edit. -->

# `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:

```sh
kubectl get displays --field-selector status.node=node-1
```

```yaml
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](/docs/reference/devices/) 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 |
| --- | --- | --- | --- |
| <span id="spec--brightness"></span>`brightness` | integer | no | The panel's brightness value. It must not exceed status.capabilities.brightness.max. |
| <span id="spec--contrast"></span>`contrast` | integer | no | The panel's own contrast number. |
| <span id="spec--sharpness"></span>`sharpness` | integer | no | The panel's own sharpness number. |
| <span id="spec--colorpreset"></span>`colorPreset` | string | no | One value from status.capabilities.colorPreset.values. |
| <span id="spec--input"></span>`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. |
| <span id="spec--audiovolume"></span>`audioVolume` | integer | no | The panel's own volume number. |
| <span id="spec--audiomute"></span>`audioMute` | boolean | no | Whether the panel's own speakers are muted. |
| <span id="spec--mode"></span>`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. |
| <span id="spec--layout"></span>`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])?$`. |
| <span id="spec--override"></span>`override` | [object](#specoverride) | 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 |
| --- | --- | --- | --- |
| <span id="specoverride--backlight"></span>`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`. |
| <span id="specoverride--power"></span>`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 |
| --- | --- | --- | --- |
| <span id="status--node"></span>`node` | string | no | The machine whose graphics card drives this panel. |
| <span id="status--connector"></span>`connector` | string | no | The connector on that card, in the kernel's own spelling. |
| <span id="status--manufacturer"></span>`manufacturer` | string | no | The monitor's manufacturer, from its EDID, the same value the device attribute carries. |
| <span id="status--model"></span>`model` | string | no | The monitor's model name, from its EDID. |
| <span id="status--serial"></span>`serial` | string | no | The monitor's serial, from its EDID, and absent when the monitor states none. |
| <span id="status--widthmillimeters"></span>`widthMillimeters` | integer | no | The panel's physical width, as the monitor states it. |
| <span id="status--heightmillimeters"></span>`heightMillimeters` | integer | no | The panel's physical height, as the monitor states it. |
| <span id="status--physicaladdress"></span>`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. |
| <span id="status--mode"></span>`mode` | [object](#statusmode) | 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. |
| <span id="status--modes"></span>`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. |
| <span id="status--capabilities"></span>`capabilities` | [map\[string\]object](#statuscapabilities) | 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. |
| <span id="status--observed"></span>`observed` | [object](#statusobserved) | 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. |
| <span id="status--captured"></span>`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. |
| <span id="status--unconfirmed"></span>`unconfirmed` | [\[\]object](#statusunconfirmed) | 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. |
| <span id="status--written"></span>`written` | [\[\]object](#statuswritten) | 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. |
| <span id="status--surfaces"></span>`surfaces` | [\[\]object](#statussurfaces) | 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. |
| <span id="status--layout"></span>`layout` | [object](#statuslayout) | no | The arrangement the screen is drawn to, and what each region shows. |
| <span id="status--conditions"></span>`conditions` | [\[\]object](#statusconditions) | 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 |
| --- | --- | --- | --- |
| <span id="statusmode--kernel"></span>`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. |
| <span id="statusmode--weston"></span>`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 |
| --- | --- | --- | --- |
| <span id="statuscapabilities--max"></span>`max` | integer | no | The largest number the panel accepts for a continuous control. |
| <span id="statuscapabilities--values"></span>`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 |
| --- | --- | --- | --- |
| <span id="statusobserved--brightness"></span>`brightness` | integer | no |  |
| <span id="statusobserved--contrast"></span>`contrast` | integer | no |  |
| <span id="statusobserved--sharpness"></span>`sharpness` | integer | no |  |
| <span id="statusobserved--colorpreset"></span>`colorPreset` | string | no |  |
| <span id="statusobserved--input"></span>`input` | string | no |  |
| <span id="statusobserved--audiovolume"></span>`audioVolume` | integer | no |  |
| <span id="statusobserved--audiomute"></span>`audioMute` | boolean | no |  |
| <span id="statusobserved--power"></span>`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 |
| --- | --- | --- | --- |
| <span id="statusunconfirmed--control"></span>`control` | string | yes | The control, in the spelling of status.capabilities, or mode for spec.mode. |
| <span id="statusunconfirmed--value"></span>`value` | string | yes | The value the operator wrote. |
| <span id="statusunconfirmed--readback"></span>`readback` | string | no | The value the device held after the write. Absent when the device did not answer. |
| <span id="statusunconfirmed--generation"></span>`generation` | integer | yes | The metadata.generation the write was made for. |
| <span id="statusunconfirmed--message"></span>`message` | string | no | The failure, in the words of the party that reported it. |
| <span id="statusunconfirmed--time"></span>`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 |
| --- | --- | --- | --- |
| <span id="statuswritten--control"></span>`control` | string | yes | The control, in the spelling of status.capabilities. |
| <span id="statuswritten--value"></span>`value` | string | yes | The declared value. |
| <span id="statuswritten--generation"></span>`generation` | integer | yes | The metadata.generation the writes were made for. |
| <span id="statuswritten--count"></span>`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 |
| --- | --- | --- | --- |
| <span id="statussurfaces--id"></span>`id` | string | yes | The window's id, which the compositor assigned. status.layout names it beside the region that shows it. |
| <span id="statussurfaces--claim"></span>`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. |
| <span id="statussurfaces--pods"></span>`pods` | []string | no | The pods that hold the claim, each as namespace/name. |
| <span id="statussurfaces--labels"></span>`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. |
| <span id="statussurfaces--size"></span>`size` | [object](#statussurfacessize) | 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. |
| <span id="statussurfaces--region"></span>`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 |
| --- | --- | --- | --- |
| <span id="statussurfacessize--width"></span>`width` | integer | no | The width in pixels. |
| <span id="statussurfacessize--height"></span>`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 |
| --- | --- | --- | --- |
| <span id="statuslayout--name"></span>`name` | string | no | The Layout in force, or default for a screen that names none or names one that does not exist. |
| <span id="statuslayout--regions"></span>`regions` | [\[\]object](#statuslayoutregions) | 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 |
| --- | --- | --- | --- |
| <span id="statuslayoutregions--name"></span>`name` | string | yes | The region's name, as the Layout states it. |
| <span id="statuslayoutregions--surface"></span>`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 |
| --- | --- | --- | --- |
| <span id="statusconditions--type"></span>`type` | string | yes |  |
| <span id="statusconditions--status"></span>`status` | string | yes | One of: `True`, `False`, `Unknown`. |
| <span id="statusconditions--reason"></span>`reason` | string | yes |  |
| <span id="statusconditions--message"></span>`message` | string | no |  |
| <span id="statusconditions--lasttransitiontime"></span>`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](/docs/reference/devices/#the-control-device) 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 `Event`s 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. |

