Skip to content

Commit ec4bd5d

Browse files
Merge pull request #245 from CarterLi/master
[macOS] add player detection
2 parents c282384 + 7f19cb1 commit ec4bd5d

File tree

11 files changed

+150
-96
lines changed

11 files changed

+150
-96
lines changed

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ endif()
272272

273273
if(APPLE)
274274
list(APPEND LIBFASTFETCH_SRC
275-
src/util/apple/cfdict_helpers.c
275+
src/util/apple/cf_helpers.c
276276
src/util/apple/osascript.m
277277
src/detection/host/host_apple.c
278278
src/detection/os/os_apple.c
@@ -369,6 +369,7 @@ if(APPLE)
369369
PRIVATE "-framework OpenGL"
370370
PRIVATE "-framework OpenCL"
371371
PRIVATE "-framework Cocoa"
372+
PRIVATE "-weak_framework MediaRemote -F /System/Library/PrivateFrameworks"
372373
)
373374
endif()
374375

src/detection/battery/battery_apple.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#include "fastfetch.h"
22
#include "battery.h"
3-
#include "util/apple/cfdict_helpers.h"
3+
#include "util/apple/cf_helpers.h"
44

55
#include <IOKit/IOKitLib.h>
66

@@ -27,18 +27,28 @@ const char* ffDetectBatteryImpl(FFinstance* instance, FFlist* results)
2727
}
2828

2929
bool boolValue;
30+
const char* error;
3031

3132
BatteryResult* battery = ffListAdd(results);
3233
ffStrbufInit(&battery->capacity);
3334
int currentCapacity, maxCapacity;
34-
if(ffCfDictGetInt(properties, CFSTR("CurrentCapacity"), &currentCapacity) &&
35-
ffCfDictGetInt(properties, CFSTR("MaxCapacity"), &maxCapacity))
36-
ffStrbufAppendF(&battery->capacity, "%.0f", currentCapacity * 100.0 / maxCapacity);
35+
36+
if ((error = ffCfDictGetInt(properties, CFSTR("MaxCapacity"), &maxCapacity)))
37+
return error;
38+
if (maxCapacity <= 0)
39+
return "Querying MaxCapacity failed";
40+
41+
if ((error = ffCfDictGetInt(properties, CFSTR("CurrentCapacity"), &currentCapacity)))
42+
return error;
43+
if(currentCapacity <= 0)
44+
return "Querying CurrentCapacity failed";
45+
46+
ffStrbufAppendF(&battery->capacity, "%.0f", currentCapacity * 100.0 / maxCapacity);
3747

3848
ffStrbufInit(&battery->manufacturer);
3949
ffStrbufInit(&battery->modelName);
4050
ffStrbufInit(&battery->technology);
41-
if (ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue)
51+
if (!ffCfDictGetBool(properties, CFSTR("built-in"), &boolValue) && boolValue)
4252
{
4353
ffStrbufAppendS(&battery->manufacturer, "Apple Inc.");
4454
ffStrbufAppendS(&battery->modelName, "Builtin");
@@ -52,9 +62,9 @@ const char* ffDetectBatteryImpl(FFinstance* instance, FFlist* results)
5262
}
5363

5464
ffStrbufInit(&battery->status);
55-
if (ffCfDictGetBool(properties, CFSTR("FullyCharged"), &boolValue) && boolValue)
65+
if (!ffCfDictGetBool(properties, CFSTR("FullyCharged"), &boolValue) && boolValue)
5666
ffStrbufAppendS(&battery->status, "Fully charged");
57-
else if (ffCfDictGetBool(properties, CFSTR("IsCharging"), &boolValue) && boolValue)
67+
else if (!ffCfDictGetBool(properties, CFSTR("IsCharging"), &boolValue) && boolValue)
5868
ffStrbufAppendS(&battery->status, "Charging");
5969
else
6070
ffStrbufAppendS(&battery->status, "");

src/detection/gpu/gpu_apple.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include "gpu.h"
22
#include "common/library.h"
33
#include "detection/cpu/cpu.h"
4-
#include "util/apple/cfdict_helpers.h"
4+
#include "util/apple/cf_helpers.h"
55

66
#include <IOKit/graphics/IOGraphicsLib.h>
77

@@ -34,7 +34,7 @@ const char* ffDetectGPUImpl(FFlist* gpus, const FFinstance* instance)
3434
ffStrbufInit(&gpu->name);
3535
//IOAccelerator returns model property for Apple Silicon, but not for Intel Iris GPUs.
3636
//Still needs testing for AMD's
37-
if(!ffCfDictGetString(properties, CFSTR("model"), &gpu->name))
37+
if(ffCfDictGetString(properties, CFSTR("model"), &gpu->name))
3838
{
3939
CFRelease(properties);
4040

@@ -49,7 +49,7 @@ const char* ffDetectGPUImpl(FFlist* gpus, const FFinstance* instance)
4949
ffCfDictGetString(properties, CFSTR("model"), &gpu->name);
5050
}
5151

52-
if(!ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount))
52+
if(ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount))
5353
gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
5454

5555
gpu->temperature = FF_GPU_TEMP_UNSET;

src/detection/media/media.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const FFMediaResult* ffDetectMedia(const FFinstance* instance)
1313
ffStrbufInit(&result.artist);
1414
ffStrbufInit(&result.album);
1515
ffStrbufInit(&result.url);
16+
ffStrbufInit(&result.status);
1617

1718
ffDetectMediaImpl(instance, &result);
1819

src/detection/media/media.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ typedef struct FFMediaResult
1414
FFstrbuf artist;
1515
FFstrbuf album;
1616
FFstrbuf url;
17+
FFstrbuf status;
1718
} FFMediaResult;
1819

1920
const FFMediaResult* ffDetectMedia(const FFinstance* instance);

src/detection/media/media_apple.m

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,66 @@
11
#include "fastfetch.h"
22
#include "detection/media/media.h"
33
#include "common/library.h"
4-
#include "util/apple/cfdict_helpers.h"
4+
#include "util/apple/cf_helpers.h"
5+
#include "util/apple/osascript.h"
56

67
#import <Foundation/Foundation.h>
78
#import <CoreFoundation/CoreFoundation.h>
89

9-
void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info));
10+
extern void MRMediaRemoteGetNowPlayingInfo(dispatch_queue_t dispatcher, void(^callback)(_Nullable CFDictionaryRef info)) __attribute__((weak_import));
11+
extern void MRMediaRemoteGetNowPlayingClient(dispatch_queue_t dispatcher, void (^callback)(_Nullable id clientObj)) __attribute__((weak_import));
12+
extern CFStringRef MRNowPlayingClientGetBundleIdentifier(id clientObj) __attribute__((weak_import));
13+
extern CFStringRef MRNowPlayingClientGetParentAppBundleIdentifier(id clientObj) __attribute__((weak_import));
14+
void MRMediaRemoteGetNowPlayingApplicationIsPlaying(dispatch_queue_t queue, void (^callback)(BOOL playing));
1015

1116
static const char* getMedia(FFMediaResult* result)
1217
{
13-
FF_LIBRARY_LOAD(MediaRemote, NULL, "dlopen MediaRemote failed", "/System/Library/PrivateFrameworks/MediaRemote.framework/MediaRemote", -1);
14-
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(MediaRemote, MRMediaRemoteGetNowPlayingInfo);
18+
#define FF_TEST_FN_EXISTANCE(fn) if (!fn) return "MediaRemote function " #fn " is not available"
19+
FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingInfo);
20+
FF_TEST_FN_EXISTANCE(MRMediaRemoteGetNowPlayingClient);
21+
FF_TEST_FN_EXISTANCE(MRNowPlayingClientGetBundleIdentifier);
22+
FF_TEST_FN_EXISTANCE(MRNowPlayingClientGetParentAppBundleIdentifier);
23+
#undef FF_TEST_FN_EXISTANCE
1524

16-
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
17-
ffMRMediaRemoteGetNowPlayingInfo(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(_Nullable CFDictionaryRef info) {
25+
dispatch_group_t group = dispatch_group_create();
26+
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
27+
28+
dispatch_group_enter(group);
29+
MRMediaRemoteGetNowPlayingApplicationIsPlaying(queue, ^(BOOL playing) {
30+
ffStrbufAppendS(&result->status, playing ? "Playing" : "Paused");
31+
dispatch_group_leave(group);
32+
});
33+
34+
dispatch_group_enter(group);
35+
MRMediaRemoteGetNowPlayingInfo(queue, ^(_Nullable CFDictionaryRef info) {
1836
if(info != nil) {
1937
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoTitle"), &result->song);
2038
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoArtist"), &result->artist);
2139
ffCfDictGetString(info, CFSTR("kMRMediaRemoteNowPlayingInfoAlbum"), &result->album);
2240
}
23-
dispatch_semaphore_signal(semaphore);
41+
dispatch_group_leave(group);
2442
});
25-
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
43+
44+
dispatch_group_enter(group);
45+
MRMediaRemoteGetNowPlayingClient(queue, ^(_Nullable id clientObj) {
46+
if (clientObj != nil) {
47+
CFStringRef identifier = MRNowPlayingClientGetBundleIdentifier(clientObj);
48+
if (identifier == nil)
49+
identifier = MRNowPlayingClientGetParentAppBundleIdentifier(clientObj);
50+
if (identifier != nil)
51+
ffCfStrGetString(identifier, &result->busNameShort);
52+
}
53+
dispatch_group_leave(group);
54+
});
55+
56+
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
57+
58+
if(result->busNameShort.length > 0)
59+
{
60+
char buf[128];
61+
snprintf(buf, sizeof(buf), "name of app id \"%s\"", result->busNameShort.chars);
62+
ffOsascript(buf, &result->player);
63+
}
2664

2765
if(result->song.length > 0)
2866
return NULL;
@@ -35,9 +73,4 @@ void ffDetectMediaImpl(const FFinstance* instance, FFMediaResult* media)
3573
FF_UNUSED(instance)
3674
const char* error = getMedia(media);
3775
ffStrbufAppendS(&media->error, error);
38-
39-
//TODO: proper detection
40-
//I already set it here, because the player module expects it to be set if the error is not set
41-
if(error == NULL)
42-
ffStrbufAppendS(&media->player, "Media Player");
4376
}

src/modules/song.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <ctype.h>
66

77
#define FF_SONG_MODULE_NAME "Media"
8-
#define FF_SONG_NUM_FORMAT_ARGS 4
8+
#define FF_SONG_NUM_FORMAT_ARGS 5
99

1010
static bool shouldIgoreChar(char c)
1111
{
@@ -89,6 +89,9 @@ void ffPrintSong(FFinstance* instance)
8989
fputs(" - ", stdout);
9090
}
9191

92+
if (media->status.length > 0)
93+
ffStrbufAppendF(&songPretty, " (%s)", media->status.chars);
94+
9295
ffStrbufPutTo(&songPretty, stdout);
9396
}
9497
else
@@ -97,7 +100,8 @@ void ffPrintSong(FFinstance* instance)
97100
{FF_FORMAT_ARG_TYPE_STRBUF, &songPretty},
98101
{FF_FORMAT_ARG_TYPE_STRBUF, &media->song},
99102
{FF_FORMAT_ARG_TYPE_STRBUF, &media->artist},
100-
{FF_FORMAT_ARG_TYPE_STRBUF, &media->album}
103+
{FF_FORMAT_ARG_TYPE_STRBUF, &media->album},
104+
{FF_FORMAT_ARG_TYPE_STRBUF, &media->status}
101105
});
102106
}
103107

src/util/apple/cf_helpers.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include "cf_helpers.h"
2+
3+
const char* ffCfStrGetString(CFStringRef str, FFstrbuf* result)
4+
{
5+
uint32_t length = (uint32_t)CFStringGetLength(str);
6+
//CFString stores UTF16 characters, therefore may require larger buffer to convert to UTF8 string
7+
ffStrbufEnsureFree(result, length * 2);
8+
if(!CFStringGetCString(str, result->chars, result->allocated, kCFStringEncodingUTF8))
9+
return "CFStringGetCString() failed";
10+
// CFStringGetCString ensures the buffer is NUL terminated
11+
// https://developer.apple.com/documentation/corefoundation/1542721-cfstringgetcstring
12+
result->length = (uint32_t) strnlen(result->chars, (uint32_t)result->allocated);
13+
return NULL;
14+
}
15+
16+
const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result)
17+
{
18+
CFTypeRef cf = (CFTypeRef)CFDictionaryGetValue(dict, key);
19+
if(cf == NULL)
20+
return "CFDictionaryGetValue() failed";
21+
22+
if(CFGetTypeID(cf) == CFStringGetTypeID())
23+
{
24+
return ffCfStrGetString((CFStringRef)cf, result);
25+
}
26+
else if(CFGetTypeID(cf) == CFDataGetTypeID())
27+
{
28+
CFDataRef cfData = (CFDataRef)cf;
29+
uint32_t length = (uint32_t)CFDataGetLength(cfData);
30+
ffStrbufEnsureFree(result, length + 1);
31+
CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*)result->chars);
32+
result->length = (uint32_t)strnlen(result->chars, length);
33+
result->chars[result->length] = '\0';
34+
return NULL;
35+
}
36+
37+
return "TypeID is neither 'CFString' nor 'CFData'";
38+
}
39+
40+
const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result)
41+
{
42+
CFBooleanRef cf = (CFBooleanRef)CFDictionaryGetValue(dict, key);
43+
if(cf == NULL || CFGetTypeID(cf) != CFBooleanGetTypeID())
44+
return "TypeID is not 'CFBoolean'";
45+
46+
*result = CFBooleanGetValue(cf);
47+
return NULL;
48+
}
49+
50+
const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result)
51+
{
52+
CFNumberRef cf = (CFNumberRef)CFDictionaryGetValue(dict, key);
53+
if (cf == NULL || CFGetTypeID(cf) != CFNumberGetTypeID())
54+
return "TypeID is not 'CFNumber'";
55+
56+
if(!CFNumberGetValue(cf, kCFNumberSInt32Type, result))
57+
return "Number type is not SInt32";
58+
return NULL;
59+
}

src/util/apple/cf_helpers.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#pragma once
2+
3+
#ifndef FASTFETCH_INCLUDED_cf_helpers
4+
#define FASTFETCH_INCLUDED_cf_helpers
5+
6+
#include "fastfetch.h"
7+
#include <CoreFoundation/CoreFoundation.h>
8+
9+
//Return error info if failed, NULL otherwise
10+
const char* ffCfStrGetString(CFStringRef str, FFstrbuf* result);
11+
const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result);
12+
const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result);
13+
const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result);
14+
15+
#endif

src/util/apple/cfdict_helpers.c

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)