Hand the idle screen to another controller

By default the media operator draws a Player’s idle screen with its own client. spec.idle.controller names what draws it instead. This guide covers both sides: the field a cluster owner sets, and the contract an operator follows to take a screen over.

You need:

Name the controller

controller is a domain-qualified name, <domain>/<name>. Set it on one Player, or on MediaPreferences as the household default. The Player wins where both are set.

spec:
  idle:
    controller: library.liken.sh/media-browser

Two names belong to the media operator:

Any other name is a delegate. The operator keeps the display claim, runs no client of its own, and writes what the delegate needs to the Player status. spec.idle.image has no effect under a delegate. library.liken.sh/media-browser is the library operator ’s media browser, the one delegate that exists today.

kubectl get players shows the resolved name in its Idle column.

Read the status

The delegate acts on status.idle and never on spec.idle. The spec may inherit its controller from MediaPreferences, and the media operator resolves the two into one name.

status:
  idle:
    controller: library.liken.sh/media-browser
    claim: den-idle-devices
    requests: [draw, render]
    fadeAfterSeconds: 600
    offAfterSeconds: 1800
    bus:
      address: bus.liken-system.svc:1883
      statusTopic: liken/media/players/den/den/status
      volumeTopic: liken/media/players/den/den/volume
      powerTopic: liken/media/players/den/den/power
      commandsTopic: liken/media/players/den/den/commands
      panelTopic: liken/media/players/den/den/panel
      remotes:
        - events: liken/media/remotes/den/den-gamepad/events
          focus: liken/media/remotes/den/den-gamepad/focus

Build the pod

Run one pod in the Player’s namespace. Reference the claim by name, and give the container one entry per request:

spec:
  resourceClaims:
    - name: devices
      resourceClaimName: den-idle-devices
  containers:
    - name: screen
      image: example.com/my-idle-screen
      resources:
        claims:
          - name: devices
            request: draw
          - name: devices
            request: render

The draw device delivers WAYLAND_DISPLAY, a compositor socket the display operator opened for this claim alone. Open it and map your window. The socket says which screen the window belongs on, so the window needs no app-id and no flag.

The draw device is shared. Your pod and a Play’s playback pod hold the screen at once, and the playback window draws over yours while media plays.

A window can go away under a running client when the compositor restarts. Nothing inside the process can open the connection again, so exit when no window exists for longer than a grace period. The operator’s own client exits with code 7 in that case, so a person reading a container’s last state finds the same code whichever client the image runs.

If your client’s exit ends its container, the kubelet counts it as a crash, and a second compositor restart within ten minutes leaves your screen dark for 10 seconds or more while the kubelet’s crash backoff runs. The operator’s own client avoids that wait: the container’s first process runs the client as a child, waits for the compositor’s socket to accept a connection, and starts the client again. Only an exit with another code ends its container.

Read the presses

The client that draws a screen also answers its controllers. It holds the focus gate, the shade, the fade and off windows, the volume indicator, the cycle request, and the panel desire, in its own process. The operator runs no pod between the bus and the client.

There are two ways to hold that contract:

Set these variables on your container. Each value comes from status.idle:

A press arrives on a controller’s events topic as {"key": "KEY_UP", "value": 1}, the same JSON every reader of the Remote’s events topic gets. A press acts only while the controller’s focus mark names this Player, only while the unit plays nothing, and only while the screen is awake. A press on a sleeping screen wakes it and does nothing else, except KEY_SLEEP in the power mode screen, which leaves the screen asleep. A held control arrives again as value 2, and a release, value 0, acts on nothing.

A live focus mark that moves to this Player wakes the screen. A repeat of the mark the client already holds wakes nothing, because the operator can publish the same mark again with no person behind it. The one repeat that acts is the answer to the client’s own cycle request, on a controller that only this Player lists.

The client brings its own shade down. The operator’s client does it on back, and on power in the power mode screen. A client with levels does it on power in that mode, and on back when back has no level left to return to.

Expect the claim to change

A ResourceClaim is immutable. When the Player’s display selector or its render request changes, the operator replaces the claim. Before it deletes the claim, it deletes every pod the claim’s status.reservedFor names, because a claim in use stays in Terminating until its holders are gone. Your pod is one of those holders.

So run the pod under something that recreates it. An operator’s next pass does that, and so does a Deployment. The replacement stays Pending until the new claim exists, then schedules against it. The same happens when a Player switches to media.liken.sh/none: the claim’s holders go, then the claim.

When the Player switches away from your name, status.idle changes and your client no longer controls the screen. Remove its pod. The operator deletes it only when it replaces the claim.

What stays with the operator

The operator keeps the display claim. It writes the focus mark for each controller and answers the cycle request. It publishes the Player status and the bus status. It writes the override on the screen’s Display from the panel desire your client publishes. A client that publishes no panel desire leaves the panel lit, because the operator writes no override without one.

The fade window, the off window, the press gate, the shade, the volume indicator, and the panel desire are the client’s. The room’s level is the operator’s: it sets the level from the volume keys and the volume/commands asks, and relays it on the volume topic.