From 1da8367f55444d9c05eb9b7f1034635101ac6d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E9=80=9A=E6=B4=B2?= Date: Fri, 27 Jun 2025 13:51:07 +0800 Subject: [PATCH] Media (macOS): simplify now playing handling Also improve performance --- CMakeLists.txt | 5 -- src/data/nowPlaying.scptd | 24 ----- src/detection/media/media_apple.m | 143 ++++++++++++++---------------- src/fastfetch_datatext.h.in | 4 - 4 files changed, 67 insertions(+), 109 deletions(-) delete mode 100644 src/data/nowPlaying.scptd diff --git a/CMakeLists.txt b/CMakeLists.txt index d766e9acf..1ceb20a63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,11 +326,6 @@ endif() fastfetch_encode_c_string("${DATATEXT_JSON_HELP}" DATATEXT_JSON_HELP) fastfetch_load_text(src/data/structure.txt DATATEXT_STRUCTURE) -if(APPLE) - # See src/detection/media/media_apple.m - fastfetch_load_text(src/data/nowPlaying.scptd DATATEXT_NOWPLAYING) -endif() - configure_file(src/fastfetch_config.h.in fastfetch_config.h @ONLY) configure_file(src/fastfetch_datatext.h.in fastfetch_datatext.h @ONLY) if(APPLE) diff --git a/src/data/nowPlaying.scptd b/src/data/nowPlaying.scptd deleted file mode 100644 index 21ce40501..000000000 --- a/src/data/nowPlaying.scptd +++ /dev/null @@ -1,24 +0,0 @@ -use scripting additions -use framework "/System/Library/PrivateFrameworks/MediaRemote.framework" - -set MRNowPlayingRequest to current application's NSClassFromString("MRNowPlayingRequest") - -if MRNowPlayingRequest's localNowPlayingItem() is missing value then - return -end if - -if MRNowPlayingRequest's localIsPlaying() then - set status to "Playing" -else - set status to "Paused" -end if - -set infoDict to MRNowPlayingRequest's localNowPlayingItem()'s nowPlayingInfo() -set bundleObj to MRNowPlayingRequest's localNowPlayingPlayerPath()'s client() - -return status & " -" & (infoDict's valueForKey:"kMRMediaRemoteNowPlayingInfoTitle" as text) & " -" & (infoDict's valueForKey:"kMRMediaRemoteNowPlayingInfoArtist" as text) & " -" & (infoDict's valueForKey:"kMRMediaRemoteNowPlayingInfoAlbum" as text) & " -" & (bundleObj's bundleIdentifier() as text) & " -" & (bundleObj's displayName() as text) diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m index 1d2356ad2..88cfe81bf 100644 --- a/src/detection/media/media_apple.m +++ b/src/detection/media/media_apple.m @@ -1,94 +1,85 @@ #include "fastfetch.h" #include "detection/media/media.h" -#include "common/library.h" #include "common/processing.h" -#include "util/apple/cf_helpers.h" -#include "util/apple/osascript.h" -#include "fastfetch_datatext.h" #import -#import -extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)) __attribute__((weak_import)); -extern void MRMediaRemoteGetNowPlayingClient(dispatch_queue_t dispatcher, void (^callback)(_Nullable id clientObj)) __attribute__((weak_import)); -extern CFStringRef MRNowPlayingClientGetBundleIdentifier(id clientObj) __attribute__((weak_import)); -extern CFStringRef MRNowPlayingClientGetParentAppBundleIdentifier(id clientObj) __attribute__((weak_import)); -void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing)) __attribute__((weak_import)); +@interface MRContentItem : NSObject +@property (nonatomic, readonly, copy) NSDictionary* nowPlayingInfo; +@end + +@interface MRClient : NSObject +@property (nonatomic, copy) NSString* bundleIdentifier; +@property (nonatomic, copy) NSString* displayName; +@end + +@interface MRPlayerPath : NSObject +@property (nonatomic, copy) MRClient* client; +@end + +@interface MRNowPlayingRequest ++ (bool)localIsPlaying; ++ (MRContentItem*)localNowPlayingItem; ++ (MRPlayerPath*)localNowPlayingPlayerPath; +@end static const char* getMediaByMediaRemote(FFMediaResult* result) { - #define FF_TEST_FN_EXISTANCE(fn) if (!fn) return "MediaRemote function " #fn " is not available" - FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo); - FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingClient); - FF_TEST_FN_EXISTANCE(MRNowPlayingClientGetBundleIdentifier); - FF_TEST_FN_EXISTANCE(MRNowPlayingClientGetParentAppBundleIdentifier); - FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying); - #undef FF_TEST_FN_EXISTANCE + if (!NSClassFromString(@"MRNowPlayingRequest")) + return "MediaRemote framework is not available"; + if (!MRNowPlayingRequest.localNowPlayingItem) + return "No media found"; + ffStrbufSetStatic(&result->status, MRNowPlayingRequest.localIsPlaying ? "Playing" : "Paused"); - dispatch_group_t group = dispatch_group_create(); - dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + NSDictionary* infoDict = MRNowPlayingRequest.localNowPlayingItem.nowPlayingInfo; + ffStrbufSetS(&result->song, infoDict[@"kMRMediaRemoteNowPlayingInfoTitle"].UTF8String); + ffStrbufSetS(&result->artist, infoDict[@"kMRMediaRemoteNowPlayingInfoArtist"].UTF8String); + ffStrbufSetS(&result->album, infoDict[@"kMRMediaRemoteNowPlayingInfoAlbum"].UTF8String); - dispatch_group_enter(group); - MRMediaRemoteGetNowPlayingApplicationIsPlaying(queue, ^(BOOL playing) { - ffStrbufSetStatic(&result->status, playing ? "Playing" : "Paused"); - dispatch_group_leave(group); - }); - - dispatch_group_enter(group); - __block const char* error = NULL; - MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) { - if(info != nil) - { - error = ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song); - if(!error) - { - ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist); - ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album); - } - } - else - error = "MRMediaRemoteGetNowPlayingInfo() failed"; - - dispatch_group_leave(group); - }); - - dispatch_group_enter(group); - MRMediaRemoteGetNowPlayingClient(queue, ^(_Nullable id clientObj) { - if (clientObj != nil) - { - CFStringRef identifier = MRNowPlayingClientGetBundleIdentifier(clientObj); - if (identifier == nil) - identifier = MRNowPlayingClientGetParentAppBundleIdentifier(clientObj); - if (identifier != nil) - ffCfStrGetString(identifier, &result->playerId); - } - dispatch_group_leave(group); - }); - - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - // Don't dispatch_release because we are using ARC - - if(result->playerId.length > 0) - { - char buf[128]; - snprintf(buf, ARRAY_SIZE(buf), "name of app id \"%s\"", result->playerId.chars); - ffOsascript(buf, &result->player); - } - - if(result->song.length > 0) - return NULL; - - return error; + MRClient* bundleObj = MRNowPlayingRequest.localNowPlayingPlayerPath.client; + ffStrbufSetS(&result->playerId, bundleObj.bundleIdentifier.UTF8String); + ffStrbufSetS(&result->player, bundleObj.displayName.UTF8String); + return NULL; } -static const char* getMediaByOsascript(FFMediaResult* result) +__attribute__((visibility("default"), used)) +int ffPrintMediaByMediaRemote(void) { + FFMediaResult media = { + .status = ffStrbufCreate(), + .song = ffStrbufCreate(), + .artist = ffStrbufCreate(), + .album = ffStrbufCreate(), + .playerId = ffStrbufCreate(), + .player = ffStrbufCreate(), + }; + if (getMediaByMediaRemote(&media) != NULL) + return 1; + ffStrbufPutTo(&media.status, stdout); + ffStrbufPutTo(&media.song, stdout); + ffStrbufPutTo(&media.artist, stdout); + ffStrbufPutTo(&media.album, stdout); + ffStrbufPutTo(&media.playerId, stdout); + ffStrbufPutTo(&media.player, stdout); + ffStrbufDestroy(&media.status); + ffStrbufDestroy(&media.song); + ffStrbufDestroy(&media.artist); + ffStrbufDestroy(&media.album); + ffStrbufDestroy(&media.playerId); + ffStrbufDestroy(&media.player); + return 0; +} + +static const char* getMediaByAuthorizedProcess(FFMediaResult* result) +{ + // #1737 + FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote()", instance.state.platform.exePath.chars); FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); - // The script must be executed by `/usr/bin/osascript`. `ffOsascript` doesn't work here + const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) { - "/usr/bin/osascript", - "-e", - FASTFETCH_DATATEXT_NOWPLAYING, + "/usr/bin/python3", // Must be signed by Apple. Homebrew python doesn't work + "-c", + script.chars, nil }); if (error) return error; @@ -107,7 +98,7 @@ void ffDetectMediaImpl(FFMediaResult* media) { const char* error; if (@available(macOS 15.4, *)) - error = getMediaByOsascript(media); + error = getMediaByAuthorizedProcess(media); else error = getMediaByMediaRemote(media); if (error) ffStrbufAppendS(&media->error, error); diff --git a/src/fastfetch_datatext.h.in b/src/fastfetch_datatext.h.in index f40367a92..5c73b9d49 100644 --- a/src/fastfetch_datatext.h.in +++ b/src/fastfetch_datatext.h.in @@ -2,7 +2,3 @@ #define FASTFETCH_DATATEXT_JSON_HELP @DATATEXT_JSON_HELP@ #define FASTFETCH_DATATEXT_STRUCTURE @DATATEXT_STRUCTURE@ - -#ifdef __APPLE__ - #define FASTFETCH_DATATEXT_NOWPLAYING @DATATEXT_NOWPLAYING@ -#endif