diff --git a/CMakeLists.txt b/CMakeLists.txt index c934526b2c..cd226a7d0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -272,7 +272,7 @@ endif() if(APPLE) list(APPEND LIBFASTFETCH_SRC - src/util/apple/cfdict_helpers.c + src/util/apple/cf_helpers.c src/util/apple/osascript.m src/detection/host/host_apple.c src/detection/os/os_apple.c @@ -369,6 +369,7 @@ if(APPLE) PRIVATE "-framework OpenGL" PRIVATE "-framework OpenCL" PRIVATE "-framework Cocoa" + PRIVATE "-weak_framework MediaRemote -F /System/Library/PrivateFrameworks" ) endif() diff --git a/src/detection/battery/battery_apple.c b/src/detection/battery/battery_apple.c index 3af71bf56e..8fa956c771 100644 --- a/src/detection/battery/battery_apple.c +++ b/src/detection/battery/battery_apple.c @@ -1,6 +1,6 @@ #include "fastfetch.h" #include "battery.h" -#include "util/apple/cfdict_helpers.h" +#include "util/apple/cf_helpers.h" #include @@ -27,18 +27,28 @@ const char* ffDetectBatteryImpl(FFinstance* instance, FFlist* results) } bool boolValue; + const char* error; BatteryResult* battery = ffListAdd(results); ffStrbufInit(&battery->capacity); int currentCapacity, maxCapacity; - if(ffCfDictGetInt(properties, CFSTR("CurrentCapacity"), ¤tCapacity) && - ffCfDictGetInt(properties, CFSTR("MaxCapacity"), &maxCapacity)) - ffStrbufAppendF(&battery->capacity, "%.0f", currentCapacity * 100.0 / maxCapacity); + + if ((error = ffCfDictGetInt(properties, CFSTR("MaxCapacity"), &maxCapacity))) + return error; + if (maxCapacity <= 0) + return "Querying MaxCapacity failed"; + + if ((error = ffCfDictGetInt(properties, CFSTR("CurrentCapacity"), ¤tCapacity))) + return error; + if(currentCapacity <= 0) + return "Querying CurrentCapacity failed"; + + ffStrbufAppendF(&battery->capacity, "%.0f", currentCapacity * 100.0 / maxCapacity); ffStrbufInit(&battery->manufacturer); ffStrbufInit(&battery->modelName); ffStrbufInit(&battery->technology); - if (ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue) + if (!ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue) { ffStrbufAppendS(&battery->manufacturer, "Apple Inc."); ffStrbufAppendS(&battery->modelName, "Builtin"); @@ -52,9 +62,9 @@ const char* ffDetectBatteryImpl(FFinstance* instance, FFlist* results) } ffStrbufInit(&battery->status); - if (ffCfDictGetBool(properties, CFSTR("FullyCharged"), &boolValue) && boolValue) + if (!ffCfDictGetBool(properties, CFSTR("FullyCharged"), &boolValue) && boolValue) ffStrbufAppendS(&battery->status, "Fully charged"); - else if (ffCfDictGetBool(properties, CFSTR("IsCharging"), &boolValue) && boolValue) + else if (!ffCfDictGetBool(properties, CFSTR("IsCharging"), &boolValue) && boolValue) ffStrbufAppendS(&battery->status, "Charging"); else ffStrbufAppendS(&battery->status, ""); diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c index e756b9467f..445df68315 100644 --- a/src/detection/gpu/gpu_apple.c +++ b/src/detection/gpu/gpu_apple.c @@ -1,7 +1,7 @@ #include "gpu.h" #include "common/library.h" #include "detection/cpu/cpu.h" -#include "util/apple/cfdict_helpers.h" +#include "util/apple/cf_helpers.h" #include @@ -34,7 +34,7 @@ const char* ffDetectGPUImpl(FFlist* gpus, const FFinstance* instance) ffStrbufInit(&gpu->name); //IOAccelerator returns model property for Apple Silicon, but not for Intel Iris GPUs. //Still needs testing for AMD's - if(!ffCfDictGetString(properties, CFSTR("model"), &gpu->name)) + if(ffCfDictGetString(properties, CFSTR("model"), &gpu->name)) { CFRelease(properties); @@ -49,7 +49,7 @@ const char* ffDetectGPUImpl(FFlist* gpus, const FFinstance* instance) ffCfDictGetString(properties, CFSTR("model"), &gpu->name); } - if(!ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount)) + if(ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount)) gpu->coreCount = FF_GPU_CORE_COUNT_UNSET; gpu->temperature = FF_GPU_TEMP_UNSET; diff --git a/src/detection/media/media.c b/src/detection/media/media.c index e82d3a5229..d1cc6ab764 100644 --- a/src/detection/media/media.c +++ b/src/detection/media/media.c @@ -13,6 +13,7 @@ const FFMediaResult* ffDetectMedia(const FFinstance* instance) ffStrbufInit(&result.artist); ffStrbufInit(&result.album); ffStrbufInit(&result.url); + ffStrbufInit(&result.status); ffDetectMediaImpl(instance, &result); diff --git a/src/detection/media/media.h b/src/detection/media/media.h index 4c29779acd..7261ca2462 100644 --- a/src/detection/media/media.h +++ b/src/detection/media/media.h @@ -14,6 +14,7 @@ typedef struct FFMediaResult FFstrbuf artist; FFstrbuf album; FFstrbuf url; + FFstrbuf status; } FFMediaResult; const FFMediaResult* ffDetectMedia(const FFinstance* instance); diff --git a/src/detection/media/media_apple.m b/src/detection/media/media_apple.m index ab9b345808..6db70d048c 100644 --- a/src/detection/media/media_apple.m +++ b/src/detection/media/media_apple.m @@ -1,28 +1,66 @@ #include "fastfetch.h" #include "detection/media/media.h" #include "common/library.h" -#include "util/apple/cfdict_helpers.h" +#include "util/apple/cf_helpers.h" +#include "util/apple/osascript.h" #import #import -void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)); +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)); static const char* getMedia(FFMediaResult* result) { - FF_LIBRARY_LOAD(MediaRemote, NULL, "dlopen MediaRemote failed", "/System/Library/PrivateFrameworks/MediaRemote.framework/MediaRemote", -1); - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(MediaRemote, MRMediaRemoteGetNowPlayingInfo); + #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); + #undef FF_TEST_FN_EXISTANCE - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - ffMRMediaRemoteGetNowPlayingInfo(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(_Nullable CFDictionaryRef info) { + dispatch_group_t group = dispatch_group_create(); + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); + + dispatch_group_enter(group); + MRMediaRemoteGetNowPlayingApplicationIsPlaying(queue, ^(BOOL playing) { + ffStrbufAppendS(&result->status, playing ? "Playing" : "Paused"); + dispatch_group_leave(group); + }); + + dispatch_group_enter(group); + MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) { if(info != nil) { ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist); ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album); } - dispatch_semaphore_signal(semaphore); + dispatch_group_leave(group); }); - dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); + + 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->busNameShort); + } + dispatch_group_leave(group); + }); + + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + if(result->busNameShort.length > 0) + { + char buf[128]; + snprintf(buf, sizeof(buf), "name of app id \"%s\"", result->busNameShort.chars); + ffOsascript(buf, &result->player); + } if(result->song.length > 0) return NULL; @@ -35,9 +73,4 @@ void ffDetectMediaImpl(const FFinstance* instance, FFMediaResult* media) FF_UNUSED(instance) const char* error = getMedia(media); ffStrbufAppendS(&media->error, error); - - //TODO: proper detection - //I already set it here, because the player module expects it to be set if the error is not set - if(error == NULL) - ffStrbufAppendS(&media->player, "Media Player"); } diff --git a/src/modules/song.c b/src/modules/song.c index b1477020c5..e4d174e5a0 100644 --- a/src/modules/song.c +++ b/src/modules/song.c @@ -5,7 +5,7 @@ #include #define FF_SONG_MODULE_NAME "Media" -#define FF_SONG_NUM_FORMAT_ARGS 4 +#define FF_SONG_NUM_FORMAT_ARGS 5 static bool shouldIgoreChar(char c) { @@ -89,6 +89,9 @@ void ffPrintSong(FFinstance* instance) fputs(" - ", stdout); } + if (media->status.length > 0) + ffStrbufAppendF(&songPretty, " (%s)", media->status.chars); + ffStrbufPutTo(&songPretty, stdout); } else @@ -97,7 +100,8 @@ void ffPrintSong(FFinstance* instance) {FF_FORMAT_ARG_TYPE_STRBUF, &songPretty}, {FF_FORMAT_ARG_TYPE_STRBUF, &media->song}, {FF_FORMAT_ARG_TYPE_STRBUF, &media->artist}, - {FF_FORMAT_ARG_TYPE_STRBUF, &media->album} + {FF_FORMAT_ARG_TYPE_STRBUF, &media->album}, + {FF_FORMAT_ARG_TYPE_STRBUF, &media->status} }); } diff --git a/src/util/apple/cf_helpers.c b/src/util/apple/cf_helpers.c new file mode 100644 index 0000000000..574ce92d37 --- /dev/null +++ b/src/util/apple/cf_helpers.c @@ -0,0 +1,59 @@ +#include "cf_helpers.h" + +const char* ffCfStrGetString(CFStringRef str, FFstrbuf* result) +{ + uint32_t length = (uint32_t)CFStringGetLength(str); + //CFString stores UTF16 characters, therefore may require larger buffer to convert to UTF8 string + ffStrbufEnsureFree(result, length * 2); + if(!CFStringGetCString(str, result->chars, result->allocated, kCFStringEncodingUTF8)) + return "CFStringGetCString() failed"; + // CFStringGetCString ensures the buffer is NUL terminated + // https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring + result->length = (uint32_t) strnlen(result->chars, (uint32_t)result->allocated); + return NULL; +} + +const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) +{ + CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); + if(cf == NULL) + return "CFDictionaryGetValue() failed"; + + if(CFGetTypeID(cf) == CFStringGetTypeID()) + { + return ffCfStrGetString((CFStringRef)cf, result); + } + else if(CFGetTypeID(cf) == CFDataGetTypeID()) + { + CFDataRef cfData = (CFDataRef)cf; + uint32_t length = (uint32_t)CFDataGetLength(cfData); + ffStrbufEnsureFree(result, length + 1); + CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); + result->length = (uint32_t)strnlen(result->chars, length); + result->chars[result->length] = '\0'; + return NULL; + } + + return "TypeID is neither 'CFString' nor 'CFData'"; +} + +const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) +{ + CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key); + if(cf == NULL || CFGetTypeID(cf) != CFBooleanGetTypeID()) + return "TypeID is not 'CFBoolean'"; + + *result = CFBooleanGetValue(cf); + return NULL; +} + +const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) +{ + CFNumberRef cf = (CFNumberRef)CFDictionaryGetValue(dict, key); + if (cf == NULL || CFGetTypeID(cf) != CFNumberGetTypeID()) + return "TypeID is not 'CFNumber'"; + + if(!CFNumberGetValue(cf, kCFNumberSInt32Type, result)) + return "Number type is not SInt32"; + return NULL; +} diff --git a/src/util/apple/cf_helpers.h b/src/util/apple/cf_helpers.h new file mode 100644 index 0000000000..49aa405f94 --- /dev/null +++ b/src/util/apple/cf_helpers.h @@ -0,0 +1,15 @@ +#pragma once + +#ifndef FASTFETCH_INCLUDED_cf_helpers +#define FASTFETCH_INCLUDED_cf_helpers + +#include "fastfetch.h" +#include + +//Return error info if failed, NULL otherwise +const char* ffCfStrGetString(CFStringRef str, 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); + +#endif diff --git a/src/util/apple/cfdict_helpers.c b/src/util/apple/cfdict_helpers.c deleted file mode 100644 index 68ee0642b3..0000000000 --- a/src/util/apple/cfdict_helpers.c +++ /dev/null @@ -1,57 +0,0 @@ -#include "cfdict_helpers.h" - -bool ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) -{ - CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key); - if(cf == NULL) - return false; - - if(CFGetTypeID(cf) == CFStringGetTypeID()) - { - CFStringRef cfStr = (CFStringRef)cf; - uint32_t length = (uint32_t)CFStringGetLength(cfStr); - //CFString stores UTF16 characters, therefore may require larger buffer to convert to UTF8 string - ffStrbufEnsureFree(result, length * 2); - if(CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8)) - { - // CFStringGetCString ensures the buffer is NUL terminated - // https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring - result->length = (uint32_t) strnlen(result->chars, (uint32_t)result->allocated); - } - } - else if(CFGetTypeID(cf) == CFDataGetTypeID()) - { - CFDataRef cfData = (CFDataRef)cf; - uint32_t length = (uint32_t)CFDataGetLength(cfData); - ffStrbufEnsureFree(result, length + 1); - CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars); - result->length = (uint32_t)strnlen(result->chars, length); - result->chars[result->length] = '\0'; - } - else - { - return false; - } - return true; -} - -bool ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) -{ - CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key); - if(cf == NULL || CFGetTypeID(cf) != CFBooleanGetTypeID()) - return false; - - *result = CFBooleanGetValue(cf); - return true; -} - -bool ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) -{ - CFNumberRef cf = (CFNumberRef)CFDictionaryGetValue(dict, key); - if (cf == NULL || CFGetTypeID(cf) != CFNumberGetTypeID()) - return false; - - if(!CFNumberGetValue(cf, kCFNumberSInt32Type, result)) - return false; - return true; -} diff --git a/src/util/apple/cfdict_helpers.h b/src/util/apple/cfdict_helpers.h deleted file mode 100644 index c552f6382e..0000000000 --- a/src/util/apple/cfdict_helpers.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#ifndef FASTFETCH_INCLUDED_cfdict_helpers -#define FASTFETCH_INCLUDED_cfdict_helpers - -#include "fastfetch.h" -#include - -bool ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result); -bool ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result); -bool ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result); - -#endif