Phase 4: Medien-Widget mit dreistufiger Kaskade

Adapter für alles inklusive Browser, AppleScript für Spotify und Musik,
Medientasten als letzte Reserve. Die Reihenfolge ist keine Vorliebe, sondern
Absicherung: Apple hat die Now-Playing-Schnittstelle in macOS 15.4 schon
einmal zugemacht. Fällt der Adapter aus, verliert das Widget seine Anzeige —
aber nicht seine Funktion. Deshalb bleiben die Transporttasten auch dann
bedienbar, wenn gar keine Metadaten da sind.

Der Fortschritt zählt zwischen den Ereignissen selbst hoch. Spike B hatte
gezeigt, dass elapsedTime nicht laufend nachgeliefert wird: ein Scrubber, der
stur den letzten Wert anzeigt, steht still, während der Titel läuft. Gerechnet
wird aus asOf und playbackRate, begrenzt auf die Titellänge — sonst zeigt die
Anzeige nach dem Ende weiter steigende Zahlen.

Teilmeldungen (diff = true) ergänzen den Stand, statt ihn zu ersetzen. Eine
Meldung mit nur der Position würde sonst Titel, Interpret und Cover löschen.

In AnyDecodable wird Bool vor Double geprüft. Andernfalls wird "playing": true
zu 1 und die Wiedergabe gilt für immer als angehalten.

Play/Pause schaltet sofort um, statt auf das nächste Ereignis zu warten. Ein
Knopf, der eine halbe Sekunde später reagiert, fühlt sich kaputt an; das echte
Ereignis korrigiert den Stand ohnehin.

Der Adapter liegt als Quellcode unter Vendor/ (BSD-3, Stand in
ONYX-VERSION.txt) und wird beim Bauen kompiliert, statt als fertige Binary
eingecheckt zu werden. Er wird bewusst nicht gelinkt: geladen wird er von
/usr/bin/perl, im eigenen Prozess greift Apples Entitlement-Prüfung.

124 Tests grün.
This commit is contained in:
Guido Schmit
2026-08-10 21:02:13 +02:00
parent 03dd7b945e
commit 433cdcc3dd
53 changed files with 4888 additions and 2 deletions

View File

@@ -0,0 +1,92 @@
cmake_minimum_required(VERSION 3.15)
project(MediaRemoteAdapter LANGUAGES OBJC)
set(ADAPTER_VERSION_MAJOR 0)
set(ADAPTER_VERSION_MINOR 1)
set(ADAPTER_VERSION_PATCH 0)
set(ADAPTER_VERSION_SHORT "${ADAPTER_VERSION_MAJOR}.${ADAPTER_VERSION_MINOR}")
set(ADAPTER_VERSION
"${ADAPTER_VERSION_MAJOR}.${ADAPTER_VERSION_MINOR}.${ADAPTER_VERSION_PATCH}"
)
set(MEDIAREMOTEADAPTER_FRAMEWORK_NAME "MediaRemoteAdapter"
CACHE STRING "The output name of the adapter framework")
set(MEDIAREMOTEADAPTER_TEST_CLIENT_NAME "MediaRemoteAdapterTestClient"
CACHE STRING "The output name of the adapter test client")
set(ADAPTER_SOURCES
src/adapter/env.m
src/adapter/get.m
src/adapter/globals.m
src/adapter/keys.m
src/adapter/now_playing.m
src/adapter/repeat.m
src/adapter/seek.m
src/adapter/send.m
src/adapter/shuffle.m
src/adapter/speed.m
src/adapter/stream.m
src/adapter/test.m
src/private/MediaRemote.m
src/utility/Debounce.m
src/utility/helpers.m)
set(HEADERS include/MediaRemoteAdapter.h)
set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64")
add_library(MediaRemoteAdapter SHARED ${ADAPTER_SOURCES})
set_target_properties(
MediaRemoteAdapter
PROPERTIES FRAMEWORK TRUE
FRAMEWORK_VERSION A
MACOSX_FRAMEWORK_IDENTIFIER
"com.vandenbe.${MEDIAREMOTEADAPTER_FRAMEWORK_NAME}"
MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${ADAPTER_VERSION_SHORT}"
MACOSX_FRAMEWORK_BUNDLE_VERSION "${ADAPTER_VERSION}"
MACOSX_FRAMEWORK_BUNDLE_NAME
"${MEDIAREMOTEADAPTER_FRAMEWORK_NAME}"
PUBLIC_HEADER "${HEADERS}"
OUTPUT_NAME "${MEDIAREMOTEADAPTER_FRAMEWORK_NAME}")
target_link_libraries(MediaRemoteAdapter "-framework Foundation"
"-framework AppKit")
find_library(UT_FRAMEWORK UniformTypeIdentifiers)
if(UT_FRAMEWORK)
target_link_libraries(MediaRemoteAdapter ${UT_FRAMEWORK})
endif()
target_include_directories(MediaRemoteAdapter
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_include_directories(MediaRemoteAdapter
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_compile_options(MediaRemoteAdapter PRIVATE "-fobjc-arc")
# Ensure that symbols are visible, otherwise the Perl script can't call into
# functions that are exported by the framework.
target_compile_options(MediaRemoteAdapter PRIVATE -fvisibility=default)
add_custom_command(
TARGET MediaRemoteAdapter
POST_BUILD
COMMAND codesign --force --deep --sign -
$<TARGET_BUNDLE_DIR:MediaRemoteAdapter>
COMMENT "Ad-hoc signing ${MEDIAREMOTEADAPTER_FRAMEWORK_NAME}.framework")
add_executable(MediaRemoteAdapterTestClient src/test/main.m
src/test/NowPlayingTest.m)
target_link_libraries(MediaRemoteAdapterTestClient
PRIVATE "-framework Foundation" "-framework MediaPlayer")
target_include_directories(MediaRemoteAdapterTestClient
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/test)
set_target_properties(MediaRemoteAdapterTestClient
PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
set_source_files_properties(src/test/main.m PROPERTIES COMPILE_FLAGS
"-fobjc-arc")
set_source_files_properties(src/test/NowPlayingTest.m PROPERTIES COMPILE_FLAGS
"-fobjc-arc")
set_target_properties(
MediaRemoteAdapterTestClient
PROPERTIES OUTPUT_NAME "${MEDIAREMOTEADAPTER_TEST_CLIENT_NAME}")