mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-09-12 10:22:12 +02:00
Media (macOS): adds length & position detection support
This commit is contained in:
+8
-2
@@ -1,7 +1,6 @@
|
||||
# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" \) -print0 | xargs -0 clang-format -i
|
||||
# find . -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.hpp" -o -name "*.m" -o -name "*.mm" \) -print0 | xargs -0 clang-format -i
|
||||
|
||||
BasedOnStyle: LLVM
|
||||
Language: Cpp
|
||||
|
||||
UseTab: Never
|
||||
IndentWidth: 4
|
||||
@@ -81,3 +80,10 @@ AttributeMacros:
|
||||
|
||||
MaxEmptyLinesToKeep: 1
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
|
||||
---
|
||||
Language: ObjC
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
|
||||
@@ -34,6 +34,29 @@ const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) {
|
||||
return "TypeID is neither 'CFNumber' nor 'CFData'";
|
||||
}
|
||||
|
||||
const char* ffCfNumGetDouble(CFTypeRef cf, double* result) {
|
||||
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
|
||||
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberDoubleType, result) &&
|
||||
!CFNumberGetValue((CFNumberRef) cf, kCFNumberFloatType, result)) {
|
||||
return "Number type is not Double or Float";
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return "TypeID is neither 'CFNumber'";
|
||||
}
|
||||
|
||||
const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result) {
|
||||
if (CFGetTypeID(cf) != CFDateGetTypeID()) {
|
||||
return "TypeID is not 'CFDate'";
|
||||
}
|
||||
|
||||
CFAbsoluteTime absTime = CFDateGetAbsoluteTime((CFDateRef) cf);
|
||||
// Convert from seconds to milliseconds and add the difference between 1970 and 2001 in milliseconds
|
||||
*result = (uint64_t) ((absTime + 978307200 /*kCFAbsoluteTimeIntervalSince1970*/) * 1000);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) {
|
||||
ffStrbufClear(result);
|
||||
if (!cf) {
|
||||
@@ -149,6 +172,15 @@ const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* res
|
||||
return ffCfNumGetInt64(cf, result);
|
||||
}
|
||||
|
||||
const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result) {
|
||||
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
|
||||
if (cf == NULL) {
|
||||
return "CFDictionaryGetValue() failed";
|
||||
}
|
||||
|
||||
return ffCfNumGetDouble(cf, result);
|
||||
}
|
||||
|
||||
const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length) {
|
||||
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
|
||||
if (cf == NULL) {
|
||||
@@ -182,3 +214,12 @@ const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryR
|
||||
*result = cf;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result) {
|
||||
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
|
||||
if (cf == NULL) {
|
||||
return "CFDictionaryGetValue() failed";
|
||||
}
|
||||
|
||||
return ffCfDateGetEpoch(cf, result);
|
||||
}
|
||||
|
||||
@@ -8,14 +8,18 @@
|
||||
const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result);
|
||||
const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result);
|
||||
const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result);
|
||||
const char* ffCfNumGetDouble(CFTypeRef cf, double* result);
|
||||
const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result);
|
||||
const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result);
|
||||
const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result);
|
||||
const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result);
|
||||
const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result);
|
||||
const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result);
|
||||
const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result);
|
||||
const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length);
|
||||
const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result);
|
||||
const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result);
|
||||
const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result);
|
||||
|
||||
static inline CFNumberRef ffCfCreateInt(int value) {
|
||||
return CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "fastfetch.h"
|
||||
#include "common/processing.h"
|
||||
#include "common/apple/cf_helpers.h"
|
||||
#include "common/time.h"
|
||||
#include "detection/media/media.h"
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -13,12 +14,31 @@ extern void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queu
|
||||
extern void MRMediaRemoteGetNowPlayingApplicationDisplayID(dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef displayID)) FF_A_WEAK_IMPORT;
|
||||
extern void MRMediaRemoteGetNowPlayingApplicationDisplayName(int unknown, dispatch_queue_t queue, void (^callback)(_Nullable CFStringRef name)) FF_A_WEAK_IMPORT;
|
||||
|
||||
static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
{
|
||||
#define FF_TEST_FN_EXISTANCE(fn) if (!fn) return "MediaRemote function " #fn " is not available"
|
||||
FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo);
|
||||
FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying);
|
||||
#undef FF_TEST_FN_EXISTANCE
|
||||
static uint32_t getTrueElapsedTime(CFDictionaryRef info) {
|
||||
double elapsedTime;
|
||||
if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoElapsedTime"), &elapsedTime) != NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
elapsedTime *= 1000;
|
||||
|
||||
double playbackRate;
|
||||
uint64_t timestampEpoch;
|
||||
if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoPlaybackRate"), &playbackRate) == NULL &&
|
||||
ffCfDictGetDateAsEpoch(info, CFSTR("kMRMediaRemoteNowPlayingInfoTimestamp"), ×tampEpoch) == NULL) {
|
||||
uint64_t timeDiff = ffTimeGetNow() - timestampEpoch;
|
||||
elapsedTime += (double) timeDiff * playbackRate;
|
||||
}
|
||||
|
||||
return (uint32_t) elapsedTime;
|
||||
}
|
||||
|
||||
static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover) {
|
||||
#define FF_TEST_FN_EXISTENCE(fn) \
|
||||
if (!fn) return "MediaRemote function " #fn " is not available"
|
||||
FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingInfo);
|
||||
FF_TEST_FN_EXISTENCE(MRMediaRemoteGetNowPlayingApplicationIsPlaying);
|
||||
#undef FF_TEST_FN_EXISTENCE
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
|
||||
@@ -26,17 +46,19 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
dispatch_group_enter(group);
|
||||
__block const char* error = NULL;
|
||||
MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) {
|
||||
if(info != nil)
|
||||
{
|
||||
if (info != nil) {
|
||||
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song);
|
||||
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist);
|
||||
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album);
|
||||
double value;
|
||||
if (ffCfDictGetDouble(info, CFSTR("kMRMediaRemoteNowPlayingInfoDuration"), &value) == NULL) {
|
||||
result->length = (uint32_t) (value * 1000);
|
||||
result->position = getTrueElapsedTime(info);
|
||||
}
|
||||
|
||||
if (saveCover)
|
||||
{
|
||||
if (saveCover) {
|
||||
NSData* artworkData = (__bridge NSData*) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkData"));
|
||||
if (artworkData)
|
||||
{
|
||||
if (artworkData) {
|
||||
CFStringRef mime = (CFStringRef) CFDictionaryGetValue(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtworkMIMEType"));
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
@@ -50,8 +72,7 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
ffStrbufSetS(&result->cover, path.UTF8String);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
} else
|
||||
error = "MRMediaRemoteGetNowPlayingInfo() failed";
|
||||
|
||||
dispatch_group_leave(group);
|
||||
@@ -63,8 +84,7 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
dispatch_group_leave(group);
|
||||
});
|
||||
|
||||
if (MRMediaRemoteGetNowPlayingApplicationDisplayID)
|
||||
{
|
||||
if (MRMediaRemoteGetNowPlayingApplicationDisplayID) {
|
||||
dispatch_group_enter(group);
|
||||
MRMediaRemoteGetNowPlayingApplicationDisplayID(queue, ^(_Nullable CFStringRef displayID) {
|
||||
ffCfStrGetString(displayID, &result->playerId);
|
||||
@@ -72,8 +92,7 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
});
|
||||
}
|
||||
|
||||
if (MRMediaRemoteGetNowPlayingApplicationDisplayName)
|
||||
{
|
||||
if (MRMediaRemoteGetNowPlayingApplicationDisplayName) {
|
||||
dispatch_group_enter(group);
|
||||
MRMediaRemoteGetNowPlayingApplicationDisplayName(0, queue, ^(_Nullable CFStringRef name) {
|
||||
ffCfStrGetString(name, &result->player);
|
||||
@@ -84,15 +103,14 @@ static const char* getMediaByMediaRemote(FFMediaResult* result, bool saveCover)
|
||||
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
|
||||
// Don't dispatch_release because we are using ARC
|
||||
|
||||
if(result->song.length > 0)
|
||||
if (result->song.length > 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
__attribute__((visibility("default"), used))
|
||||
int ffPrintMediaByMediaRemote(bool saveCover)
|
||||
{
|
||||
__attribute__((visibility("default"), used)) int ffPrintMediaByMediaRemote(bool saveCover) {
|
||||
FFMediaResult media = {
|
||||
.status = ffStrbufCreate(),
|
||||
.song = ffStrbufCreate(),
|
||||
@@ -102,15 +120,28 @@ int ffPrintMediaByMediaRemote(bool saveCover)
|
||||
.player = ffStrbufCreate(),
|
||||
.cover = ffStrbufCreate(),
|
||||
};
|
||||
if (getMediaByMediaRemote(&media, saveCover) != NULL)
|
||||
if (getMediaByMediaRemote(&media, saveCover) != 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);
|
||||
ffStrbufWriteTo(&media.cover, stdout);
|
||||
}
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppend(&media.status, &media.song);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppend(&media.status, &media.artist);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppend(&media.status, &media.album);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppend(&media.status, &media.playerId);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppend(&media.status, &media.player);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
if (saveCover) {
|
||||
ffStrbufAppend(&media.status, &media.cover);
|
||||
}
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppendUInt(&media.status, media.position);
|
||||
ffStrbufAppendC(&media.status, '\n');
|
||||
ffStrbufAppendUInt(&media.status, media.length);
|
||||
write(STDOUT_FILENO, media.status.chars, media.status.length);
|
||||
ffStrbufDestroy(&media.status);
|
||||
ffStrbufDestroy(&media.song);
|
||||
ffStrbufDestroy(&media.artist);
|
||||
@@ -121,46 +152,55 @@ int ffPrintMediaByMediaRemote(bool saveCover)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover)
|
||||
{
|
||||
static const char* getMediaByAuthorizedProcess(FFMediaResult* result, bool saveCover) {
|
||||
// #1737
|
||||
FF_STRBUF_AUTO_DESTROY script = ffStrbufCreateF("import ctypes;ctypes.CDLL('%s').ffPrintMediaByMediaRemote(%s)", instance.state.platform.exePath.chars, saveCover ? "True" : "False");
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
|
||||
|
||||
const char* error = ffProcessAppendStdOut(&buffer, (char* const[]) {
|
||||
"/usr/bin/python3", // Must be signed by Apple. Homebrew python doesn't work
|
||||
const char* error = ffProcessAppendStdOut(
|
||||
&buffer,
|
||||
(char* const[]) { "/usr/bin/python3", // Must be signed by Apple. Homebrew python doesn't work
|
||||
"-c",
|
||||
script.chars,
|
||||
nil
|
||||
});
|
||||
if (error) return error;
|
||||
if (buffer.length == 0) return "No media found";
|
||||
nil });
|
||||
if (error) {
|
||||
return error;
|
||||
}
|
||||
if (buffer.length == 0) {
|
||||
return "No media found";
|
||||
}
|
||||
|
||||
// status\ntitle\nartist\nalbum\nbundleName\nappName
|
||||
FFstrbuf* const varList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover };
|
||||
// status\ntitle\nartist\nalbum\nbundleName\nappName\ncoverPath\nelapsedTimeMS\ndurationMS
|
||||
FFstrbuf* const strList[] = { &result->status, &result->song, &result->artist, &result->album, &result->playerId, &result->player, &result->cover };
|
||||
char* line = NULL;
|
||||
size_t len = 0;
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(varList) && ffStrbufGetline(&line, &len, &buffer); ++i)
|
||||
ffStrbufSetS(varList[i], line);
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(strList) && ffStrbufGetline(&line, &len, &buffer); ++i) {
|
||||
ffStrbufSetS(strList[i], line);
|
||||
}
|
||||
uint32_t* const numList[] = { &result->position, &result->length };
|
||||
for (uint32_t i = 0; i < ARRAY_SIZE(numList) && ffStrbufGetline(&line, &len, &buffer); ++i) {
|
||||
*numList[i] = (uint32_t) strtoul(line, NULL, 10);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void ffDetectMediaImpl(FFMediaResult* media, bool saveCover)
|
||||
{
|
||||
void ffDetectMediaImpl(FFMediaResult* media, bool saveCover) {
|
||||
const char* error;
|
||||
if (@available(macOS 15.4, *))
|
||||
if (@available(macOS 15.4, *)) {
|
||||
error = getMediaByAuthorizedProcess(media, saveCover);
|
||||
else
|
||||
} else {
|
||||
error = getMediaByMediaRemote(media, saveCover);
|
||||
if (error)
|
||||
}
|
||||
if (error) {
|
||||
ffStrbufAppendS(&media->error, error);
|
||||
else if (media->player.length == 0 && media->playerId.length > 0)
|
||||
{
|
||||
} else if (media->player.length == 0 && media->playerId.length > 0) {
|
||||
ffStrbufSet(&media->player, &media->playerId);
|
||||
if (ffStrbufStartsWithIgnCaseS(&media->player, "com."))
|
||||
if (ffStrbufStartsWithIgnCaseS(&media->player, "com.")) {
|
||||
ffStrbufSubstrAfter(&media->player, strlen("com.") - 1);
|
||||
}
|
||||
ffStrbufReplaceAllC(&media->player, '.', ' ');
|
||||
if (media->cover.length > 0)
|
||||
if (media->cover.length > 0) {
|
||||
media->removeCoverAfterUse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user