Skip to content

Driverless media keys, part 1: the session posts them (phase 6) - #1325

Merged
malpern merged 1 commit into
masterfrom
feat/driverless-media-keys
Oct 20, 2026
Merged

malpern merged 1 commit into
masterfrom
feat/driverless-media-keys

Conversation

@malpern

@malpern malpern commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Phase 6, part 1 (review §4.5; measurements in driverless-media-keys.md).

What

  • Bridge. oscode_supported accepts page-7 keys in the host's map or the ten consumer-page codes in CONSUMER_OUTPUT_CODES (volume up/down/mute, brightness up/down, play/pause, next/previous, fast-forward/rewind). The table is a constant — what can be posted is fixed by the worker's code, not the keyboard. Inputs stay keyboard-page until part 2.
  • Worker. SessionOutputState gains a media ledger (translateMedia, heldMediaUsages, releaseAllMedia; value 2 dropped since macOS repeats a held media key itself). SessionRuntimeWorker.postMedia builds NSEvent.otherEvent(.systemDefined, subtype 8, data1: keyType << 16 | 0xA/0xB << 8) from SessionKeyMap.consumerUsageToMediaKeyType, tags it with the worker's output tag and posts it at the session tap; cleanup releases held media keys like keys.
  • Everything above the bridge flipped by itself (phase 1's point): the Function layer, media mode of macOS Function Keys and the eight media system actions are available; the old hard-coded refusals survive only as the no-bridge fallback, and tests pin both answers.
  • Spike follow-up: the QMKKeycodeMapping 21/22 comment now says illumination, not brightness.

Tests

  • Rust: 24 (new: every table entry pinned to its Kanata name; media is an output but not yet an input; calc refused).
  • Swift: SessionOutputStateTests media cases (translation, repeat drop, cleanup, event encoding); profile/catalog/system-action goldens flipped; SessionBridgeTestFixture.profile so admission tests don't depend on an installed app's bridge; tests that used a media key as the "unsupported output" fixture now use fn.
  • KEYPATH_TEST_ENFORCE_CLEAN_SUMMARY=1 Scripts/run-tests-safe.sh: 5,780 passed, 0 app warnings/errors, 0 build warnings. check-accessibility.py clean.

Still to do on hardware (Air)

F10/F11/F12 and brightness from a rule; held-repeat; keypath doctor showing no media verdicts.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T13:28:01.980282Z 9ceab8c PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ceab8c3d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// their own ledger so a stop releases them too.
var nextOutputs = outputs
guard let media = try nextOutputs.translateMedia(event) else { continue }
if media.isDown { outputs = nextOutputs; writeReport(.running) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist held media usages in the crash ledger

When the worker crashes or is force-terminated while a media key is held, this writes a report after recording the media down, but writeReport serializes only outputs.heldUsages; recoverSessionOutputs likewise releases only report.heldOutputUsages. Because macOS repeats a posted media down until it receives an up, the missing media ledger can leave volume or brightness continuously changing after the worker dies. Include held consumer usages in SessionRuntimeReport and have the parent recovery path post their system-defined up events.

Useful? React with 👍 / 👎.

The bridge's output rule accepts the ten consumer-page keys the worker can
post (volume up/down/mute, brightness up/down, play/pause, next/previous,
fast-forward/rewind), a fixed table in session_config.rs. The worker keeps a
media ledger beside its key ledger and posts each as an NSEvent
system-defined (subtype 8) event tagged like a key event, dropping Kanata's
repeats because macOS repeats a held media key itself; cleanup releases held
media keys like keys. Because the capability profile asks the bridge, the
catalog's Function layer, media mode of macOS Function Keys and the eight
media system actions became available with no Swift change; the hard-coded
refusals remain only as the no-bridge fallback. Inputs stay keyboard-page
until part 2 (tap reads system-defined events).

Tests that used a media key as their "unsupported output" fixture now use Fn,
which the generator passes through and the bridge refuses. See
docs/architecture/rules-admission-review.md §4.5 and
docs/architecture/driverless-media-keys.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@malpern
malpern force-pushed the feat/driverless-media-keys branch from 9ceab8c to 7f8be46 Compare October 20, 2026 13:32
@claude

claude Bot commented Oct 20, 2026

Copy link
Copy Markdown

Review of #1325 (media keys as session outputs): no blocking findings.

I checked the NX key types in consumerUsageToMediaKeyType, the Rust CONSUMER_OUTPUT_CODES, the down/up ordering in the worker, and the cleanup path. They are consistent, and the cleanup test covers a release that fails to post.

Minor notes, none blocking:

  • mediaEvent always sends modifierFlags: []. A held Shift or Option won't reach the media event, so Shift+Option+volume for fine steps won't work on remapped keys. You could OR in outputs.modifierFlags if that matters.
  • A repeat (value 2) for a media key that isn't held throws repeatWithoutPress. I couldn't confirm from the diff that the surrounding catch treats this the same way as the existing keyboard ledger. Worth confirming the two behave the same.
  • Several tests pass only with the fixture bridge. Please confirm the CI lane that builds the Rust bridge runs SystemActionInfoTests and SessionCapsGeneratedConfigTests, since requireAvailable() skips them otherwise.

@malpern
malpern merged commit b691d99 into master Oct 20, 2026
6 checks passed
@malpern
malpern deleted the feat/driverless-media-keys branch October 20, 2026 13:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant