Map a new controller

A controller works before it has a Keymap. The Remote’s pod passes every KEY_* code through under the kernel’s own name, turns the hat axes into the arrows, and reads a gamepad’s south and east buttons as enter and back. This guide finds the codes that a controller sends, and writes a Keymap row only for each button that this default mapping gets wrong or leaves out. At the end, every button on the controller does what its label says.

You need:

The controller keeps working while you map it. Discovery changes what the pod logs and leaves what it publishes the same, so a controller in discovery still drives its unit.

Declare the Remote

A Remote needs no Keymap to work. Declare it with the device alone, using your cluster’s class for controllers. The arrows, OK, back, the volume keys, and every other control the controller reports under a kernel name already work:

kubectl apply -f - <<EOF
apiVersion: media.liken.sh/v1alpha1
kind: Remote
metadata:
  name: den-remote
  namespace: media
spec:
  device:
    class: bluetooth-input
EOF

Turn on discovery

kubectl patch remote den-remote --type=merge \
  -p '{"spec":{"discovery":true}}'

The patch replaces the controller pod, which drops controller input for a few seconds. The new pod stays Pending until the controller connects for the first time, so press a button to wake it.

Press every button

Follow the pod’s log and press each button in turn:

kubectl logs -f den-remote-remote

The pod logs one verdict line per input node, then the node connecting, then each press:

remote: event3 "Handheld Remote" keep: 58 key codes, no hat axes
remote: controller connected on event3 "Handheld Remote": 58 key codes, no hat axes
remote: event3 "Handheld Remote" EV_KEY (1) BTN_SOUTH (304) press (1)
remote:   - press: BTN_SOUTH   # code 304
remote:     key: <a KEY_* name, or none>
remote: event3 "Handheld Remote": BTN_SOUTH (304) pressed, published KEY_ENTER to liken/media/remotes/media/den-remote/events

The two indented lines are a Keymap row. Paste it under spec.buttons, or under spec.axes for a hat direction. Then replace the key line with the kernel name the control should report, or with none to drop the control. The last line names what the pod published for the press, so check it before you paste the row: it already publishes KEY_ENTER for BTN_SOUTH above, the base mapping’s own answer for a gamepad’s south button, so that row is not needed. Only a press earns this last line. A release or a repeat logs the raw line above it instead, naming the code and saying a Keymap binds the press alone.

A Bluetooth LE remote that wakes from sleep does not send the press that woke it. bluetooth-operator writes a press of KEY_UNKNOWN (code 240) in its place, so discovery logs that press after each wake. It is not a button to map. A Keymap row on KEY_UNKNOWN acts on every wake, and on any real button that the kernel also reports as KEY_UNKNOWN, so do not bind it.

If the controller has modes, press every button in every mode. A combined remote can emit different codes for one button per mode. An air-mouse shell emits BTN_LEFT for its OK button in mouse mode, and KEY_ENTER in keys mode. KEY_ENTER passes on its own. BTN_LEFT is a mouse button and means nothing to a screen, so it needs a row that makes it KEY_ENTER.

Read the codes you did not press

kubectl get remote den-remote -o yaml

status.unbound lists every declared control that the base and the Keymap together map to nothing. It includes a hat axis with no row, a control whose row sets none, and a code the kernel gives no name. A declared KEY_* code passes as itself, so it never appears in the list. A keyboard remote starts with an empty list. Each entry has the code, its evdev name, and its event type.

Write the Keymap

Collect the rows into a Keymap for the model and apply it. This one renames the air-mouse click and gives the shell’s escape key nothing to do:

kubectl apply -f - <<EOF
apiVersion: media.liken.sh/v1alpha1
kind: Keymap
metadata:
  name: handheld-remote
spec:
  buttons:
    - press: BTN_LEFT
      key: KEY_ENTER
    - press: KEY_ESC
      key: none
EOF

Once the table reaches the pod, a press of the escape key logs what it drops instead of what it would otherwise pass through:

remote: event3 "Handheld Remote": KEY_ESC (1) pressed, published nothing, because the key table maps it to none

A row on a gamepad button that a person holds, a bumper that seeks for example, adds a repeat block, because a gamepad never autorepeats in the kernel:

- press: BTN_TR
  key: KEY_FASTFORWARD
  repeat:
    delay: 400ms
    interval: 250ms

Then name it on the Remote and turn discovery off:

kubectl patch remote den-remote --type=merge \
  -p '{"spec":{"keymap":"handheld-remote","discovery":false}}'

status.unbound remains after discovery ends, so it continues to show which controller controls do nothing. On a keyboard remote, it lists only the controls you dropped with none.