Skip to content

fw/services: offer music to every phone without a media service - #1989

Open
zunda-pixel wants to merge 1 commit into
coredevices:mainfrom
zunda-pixel:music-endpoint-any-os
Open

fw/services: offer music to every phone without a media service#1989
zunda-pixel wants to merge 1 commit into
coredevices:mainfrom
zunda-pixel:music-endpoint-any-os

Conversation

@zunda-pixel

@zunda-pixel zunda-pixel commented Sep 3, 2026

Copy link
Copy Markdown

music_endpoint_handle_mobile_app_info_event claims the music server only when the phone says Android, so a desktop companion app gets nothing at all: not this endpoint, because of the check, and not the Apple Media Service, because it does not publish one. s_connected stays false and music_protocol_msg_callback returns at its first line, so commands and NowPlaying writes are both dropped.

The OS the phone declares has been a first-class value for as long as RemoteOS has had RemoteOSX, RemoteOSLinux and RemoteOSWindows in it, and none of the three is reachable today.

Why the condition wants to be about alternatives

iOS is the one platform with somewhere else to read music from, and AMS is not gated on the declared OS at all — RemoteOSiOS appears in no .c file under src/fw. AMS connects when its GATT service is discovered, and whichever side calls music_set_connected_server last wins; that is exactly why this function calls ams_music_disconnect() before claiming it.

So the check becomes: iOS keeps AMS, an app that declares nothing is left alone in case it is an older iOS one, and everything else — a desktop, or Android — gets the endpoint it has no substitute for.

Verified in QEMU, both ways

Built qemu_emery twice off master, differing only in this hunk, and connected the same client to each. The client declares macOS in its PhoneVersion response, and the observable is unprompted: the firmware asks a phone it will talk music to for its state, so a frame on endpoint 32 arriving by itself is the answer.

firmware endpoints received music endpoint payload
this branch 16×1 32×1 08MusicEndpointCmdIDGetAllInfo
master 16×1 none

Tests

Two cases in test_music_endpoint, next to the iOS one that is already there:

  • request_now_playing_from_desktop_appRemoteOSX connects, state is requested
  • ignore_now_playing_from_app_of_unknown_osRemoteOSUnknown connects, nothing is handled
ctest --test-dir build-test -R test_music_endpoint
1/1 Test #163: test_music_endpoint .............. Passed

Reverting the hunk fails request_now_playing_from_desktop_app and only that one, so the pair pins the change and the boundary it keeps.

The existing helper still takes its bool is_android, so the other fifteen call sites are untouched; the new tests use an os-taking one it now delegates to.

test_music_asterix/obelix/gabbro fail in my tree on image comparisons, which is my -DPBL_TEST_IMAGES=OFF configuration — those binaries do not link music_endpoint_handle_mobile_app_info_event at all, so this cannot reach them.

On hardware, a Mac leaves the watch with nothing

I could not try the patched firmware on a watch, but I could try what a Mac gets without it. A Pebble Time 2 on v4.36.2, connected to a macOS companion over PPoG, with music playing on the Mac:

23:38:33  ppog session open
23:38:33  in  endpoint=17 (1 byte)    <- the watch asks for the phone's version
23:38:33  out endpoint=17 (25 bytes)  <- answered, declaring macOS
23:38:34  out endpoint=32 x4          <- player info, play state, volume, now playing
          in  endpoint=32: none
23:44:33  link dropped (watch restarted)
23:44:35  ppog session open, endpoint 17 exchanged again
23:44:36  out endpoint=32 x4
          in  endpoint=32: none

The watch's music app stayed empty through both sessions, the second one after a fresh boot, so it is not a stale discovery. So whatever macOS does or does not publish, the watch ends up with no music source at all: not this endpoint, because of the check, and not another one either.

I still have not inspected macOS's GATT database, so I am not claiming it publishes no AMS — only that a watch talking to a Mac gets nothing, which is the gap this closes. If it turns out macOS does publish one, ams_music_disconnect() hands music to the endpoint, which is what a desktop companion wants anyway: it can drive the machine's own player.

Found while writing an independent Swift companion for iOS and macOS, where the macOS build has no way to control music. Written with AI assistance; the QEMU comparison and the test runs above are mine and reproducible.

The music endpoint only claimed the music server when the phone said Android,
so a desktop companion got nothing: neither this endpoint, because of the
check, nor the Apple Media Service, because it does not publish one. The OS it
declares has been a first-class value for as long as the enum has had
RemoteOSX, RemoteOSLinux and RemoteOSWindows in it.

iOS is the one that has somewhere else to read from, and AMS is not gated on
the declared OS at all — it connects when its GATT service is found, and
whichever side claims music_set_connected_server last wins. So the condition
wants to be about who has an alternative rather than about Android: iOS keeps
AMS, an app that does not say what it runs on is left alone in case it is an
older iOS one, and everything else gets the endpoint.

Covered both ways: a desktop app is now asked for its state on connect, and
one that declares nothing still is not.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: zunda pixel <zunda.dev@gmail.com>
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