Media (macOS): simplify now playing handling

Also improve performance
This commit is contained in:
李通洲
2025-06-27 13:51:07 +08:00
parent bdbc885811
commit 1da8367f55
4 changed files with 67 additions and 109 deletions
-5
View File
@@ -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)
-24
View File
@@ -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)
+67 -76
View File
@@ -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 <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
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 <NSCopying>
@property (nonatomic, readonly, copy) NSDictionary<NSString*, NSString*>* nowPlayingInfo;
@end
@interface MRClient : NSObject <NSCopying, NSSecureCoding>
@property (nonatomic, copy) NSString* bundleIdentifier;
@property (nonatomic, copy) NSString* displayName;
@end
@interface MRPlayerPath : NSObject <NSCopying, NSSecureCoding>
@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<NSString*, NSString*>* 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);
-4
View File
@@ -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