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.
54 lines
1.8 KiB
Objective-C
54 lines
1.8 KiB
Objective-C
// Copyright (c) 2025 Alexander5015
|
|
// This file is licensed under the BSD 3-Clause License.
|
|
|
|
#import <Foundation/Foundation.h>
|
|
|
|
#import "NowPlayingTest.h"
|
|
|
|
static const NSTimeInterval kRunLoopInterval = 0.1;
|
|
static const size_t kInputBufferSize = 256;
|
|
int main(int argc, const char *argv[]) {
|
|
@autoreleasepool {
|
|
dup2(STDOUT_FILENO, STDERR_FILENO);
|
|
|
|
NowPlayingPublishTest *test = [[NowPlayingPublishTest alloc] init];
|
|
puts("setup_done");
|
|
fflush(stdout);
|
|
|
|
BOOL shouldExit = NO;
|
|
while (!shouldExit) {
|
|
NSDate *waitUntil =
|
|
[NSDate dateWithTimeIntervalSinceNow:kRunLoopInterval];
|
|
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
|
|
beforeDate:waitUntil];
|
|
|
|
fd_set fds;
|
|
struct timeval tv = {0, 0};
|
|
FD_ZERO(&fds);
|
|
FD_SET(STDIN_FILENO, &fds);
|
|
int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
|
|
if (ret > 0 && FD_ISSET(STDIN_FILENO, &fds)) {
|
|
char buf[kInputBufferSize];
|
|
if (fgets(buf, sizeof(buf), stdin)) {
|
|
NSString *command =
|
|
[[NSString alloc] initWithUTF8String:buf];
|
|
command = [command
|
|
stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
if ([command isEqualToString:@"cleanup"]) {
|
|
printf("cleanup_done\n");
|
|
fflush(stdout);
|
|
shouldExit = YES;
|
|
break;
|
|
} else {
|
|
|
|
puts("unknown_command");
|
|
fflush(stdout);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|