summaryrefslogtreecommitdiffstats
path: root/src/common/apple
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/apple')
-rw-r--r--src/common/apple/Info.plist.in16
-rw-r--r--src/common/apple/cf_helpers.c225
-rw-r--r--src/common/apple/cf_helpers.h43
-rw-r--r--src/common/apple/osascript.h5
-rw-r--r--src/common/apple/osascript.m15
-rw-r--r--src/common/apple/smc_temps.c465
-rw-r--r--src/common/apple/smc_temps.h32
-rw-r--r--src/common/apple/version.h5
-rw-r--r--src/common/apple/version.m43
9 files changed, 849 insertions, 0 deletions
diff --git a/src/common/apple/Info.plist.in b/src/common/apple/Info.plist.in
new file mode 100644
index 0000000..5220053
--- /dev/null
+++ b/src/common/apple/Info.plist.in
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>CFBundleIdentifier</key>
+ <string>fastfetch</string>
+ <key>CFBundleName</key>
+ <string>@PROJECT_NAME@</string>
+ <key>CFBundleShortVersionString</key>
+ <string>@PROJECT_VERSION@</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>NSBluetoothAlwaysUsageDescription</key>
+ <string>For detecting Bluetooth devices</string>
+ </dict>
+</plist>
diff --git a/src/common/apple/cf_helpers.c b/src/common/apple/cf_helpers.c
new file mode 100644
index 0000000..60e1a1c
--- /dev/null
+++ b/src/common/apple/cf_helpers.c
@@ -0,0 +1,225 @@
+#include "cf_helpers.h"
+
+const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result) {
+ if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
+ if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt64Type, result)) {
+ return "Number type is not SInt64";
+ }
+ return NULL;
+ } else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
+ if (CFDataGetLength((CFDataRef) cf) != sizeof(int64_t)) {
+ return "Data length is not sizeof(int64_t)";
+ }
+ CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(int64_t)), (uint8_t*) result);
+ return NULL;
+ }
+
+ return "TypeID is neither 'CFNumber' nor 'CFData'";
+}
+
+const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) {
+ if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
+ if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt32Type, result)) {
+ return "Number type is not SInt32";
+ }
+ return NULL;
+ } else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
+ if (CFDataGetLength((CFDataRef) cf) != sizeof(*result)) {
+ return "Data length is not sizeof(int32_t)";
+ }
+ CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(*result)), (uint8_t*) result);
+ return NULL;
+ }
+
+ 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) {
+ return NULL;
+ }
+
+ if (CFGetTypeID(cf) == CFStringGetTypeID()) {
+ CFStringRef cfStr = (CFStringRef) cf;
+
+ const char* cstr = CFStringGetCStringPtr(cfStr, kCFStringEncodingUTF8);
+ if (cstr) {
+ ffStrbufSetS(result, cstr);
+ } else {
+ uint32_t length = (uint32_t) CFStringGetLength(cfStr);
+ if (length == 0) {
+ return NULL;
+ }
+ ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8));
+ if (!CFStringGetCString(cfStr, 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);
+ }
+ } else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
+ CFDataRef cfData = (CFDataRef) cf;
+ uint32_t length = (uint32_t) CFDataGetLength(cfData);
+ if (length == 0) {
+ return NULL;
+ }
+ ffStrbufEnsureFixedLengthFree(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 "TypeID is neither 'CFString' nor 'CFData'";
+ }
+
+ return NULL;
+}
+
+const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) {
+ ffStrbufClear(result);
+ if (!cf) {
+ return NULL;
+ }
+
+ if (CFGetTypeID(cf) == CFDataGetTypeID()) {
+ CFDataRef cfData = (CFDataRef) cf;
+ uint32_t length = (uint32_t) CFDataGetLength(cfData);
+ if (length == 0) {
+ return NULL;
+ }
+ ffStrbufEnsureFixedLengthFree(result, length + 1);
+ CFDataGetBytes(cfData, CFRangeMake(0, length), (uint8_t*) result->chars);
+ result->length = length;
+ result->chars[result->length] = '\0';
+ } else {
+ return "TypeID is not 'CFData'";
+ }
+
+ return NULL;
+}
+
+const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
+ CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ return ffCfStrGetString(cf, result);
+}
+
+const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
+ CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ return ffCfDataGetDataAsString(cf, result);
+}
+
+const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) {
+ CFBooleanRef cf = (CFBooleanRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ if (CFGetTypeID(cf) != CFBooleanGetTypeID()) {
+ return "TypeID is not 'CFBoolean'";
+ }
+
+ *result = CFBooleanGetValue(cf);
+ return NULL;
+}
+
+const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) {
+ CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ return ffCfNumGetInt(cf, result);
+}
+
+const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result) {
+ CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ 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) {
+ return "CFDictionaryGetValue() failed";
+ }
+
+ if (CFGetTypeID(cf) != CFDataGetTypeID()) {
+ return "TypeID is not 'CFData'";
+ }
+
+ CFIndex trueLength = CFDataGetLength((CFDataRef) cf);
+
+ if (trueLength < offset + size) {
+ return "Data length is less than offset + size";
+ }
+
+ if (length) {
+ *length = (uint32_t) trueLength;
+ }
+
+ CFDataGetBytes((CFDataRef) cf, CFRangeMake(offset, size), result);
+ return NULL;
+}
+
+const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result) {
+ CFDictionaryRef cf = (CFDictionaryRef) CFDictionaryGetValue(dict, key);
+ if (cf == NULL || CFGetTypeID(cf) != CFDictionaryGetTypeID()) {
+ return "TypeID is not 'CFDictionary'";
+ }
+
+ *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);
+}
diff --git a/src/common/apple/cf_helpers.h b/src/common/apple/cf_helpers.h
new file mode 100644
index 0000000..a929f03
--- /dev/null
+++ b/src/common/apple/cf_helpers.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "fastfetch.h"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+
+// Return error info if failed, NULL otherwise
+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);
+}
+
+static inline void cfReleaseWrapper(void* type) {
+ assert(type);
+ if (*(CFTypeRef*) type) {
+ CFRelease(*(CFTypeRef*) type);
+ }
+}
+
+#define FF_CFTYPE_AUTO_RELEASE FF_A_CLEANUP(cfReleaseWrapper)
+
+static inline void wrapIoObjectRelease(io_object_t* service) {
+ assert(service);
+ if (*service) {
+ IOObjectRelease(*service);
+ }
+}
+#define FF_IOOBJECT_AUTO_RELEASE FF_A_CLEANUP(wrapIoObjectRelease)
diff --git a/src/common/apple/osascript.h b/src/common/apple/osascript.h
new file mode 100644
index 0000000..164d8d7
--- /dev/null
+++ b/src/common/apple/osascript.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "fastfetch.h"
+
+bool ffOsascript(const char* input, FFstrbuf* result);
diff --git a/src/common/apple/osascript.m b/src/common/apple/osascript.m
new file mode 100644
index 0000000..f17af03
--- /dev/null
+++ b/src/common/apple/osascript.m
@@ -0,0 +1,15 @@
+#include "osascript.h"
+
+#import <Foundation/Foundation.h>
+
+bool ffOsascript(const char* input, FFstrbuf* result)
+{
+ NSAppleScript* script = [NSAppleScript.alloc initWithSource:@(input)];
+ NSDictionary* errInfo = nil;
+ NSAppleEventDescriptor* descriptor = [script executeAndReturnError:&errInfo];
+ if (errInfo)
+ return false;
+
+ ffStrbufSetS(result, descriptor.stringValue.UTF8String);
+ return true;
+}
diff --git a/src/common/apple/smc_temps.c b/src/common/apple/smc_temps.c
new file mode 100644
index 0000000..6669c31
--- /dev/null
+++ b/src/common/apple/smc_temps.c
@@ -0,0 +1,465 @@
+#include "smc_temps.h"
+#include "common/apple/cf_helpers.h"
+#include "common/strutil.h"
+
+#include <stdint.h>
+#include <IOKit/IOKitLib.h>
+
+static const char kSmcCmdReadBytes = 5;
+static const char kSmcCmdReadKeyInfo = 9;
+static const uint32_t kKernelIndexSmc = 2;
+
+typedef struct
+{
+ char major;
+ char minor;
+ char build;
+ char reserved[1];
+ uint16_t release;
+} SmcKeyData_vers_t;
+
+typedef struct
+{
+ uint16_t version;
+ uint16_t length;
+ uint32_t cpuPLimit;
+ uint32_t gpuPLimit;
+ uint32_t memPLimit;
+} SmcKeyData_pLimitData_t;
+
+typedef struct
+{
+ uint32_t dataSize;
+ uint32_t dataType;
+ char dataAttributes;
+} SmcKeyData_keyInfo_t;
+
+typedef unsigned char SmcBytes_t[32];
+
+typedef struct
+{
+ uint32_t key;
+ SmcKeyData_vers_t vers;
+ SmcKeyData_pLimitData_t pLimitData;
+ SmcKeyData_keyInfo_t keyInfo;
+ char result;
+ char status;
+ char data8;
+ uint32_t data32;
+ SmcBytes_t bytes;
+} SmcKeyData_t;
+
+typedef char UInt32Char_t[5];
+
+typedef struct
+{
+ UInt32Char_t key;
+ uint32_t dataSize;
+ UInt32Char_t dataType;
+ SmcBytes_t bytes;
+} SmcVal_t;
+
+static uint32_t smcStrtoul(const char* str, int size, int base) {
+ uint32_t total = 0;
+
+ for (int i = 0; i < size; i++) {
+ if (base == 16) {
+ total += (uint32_t) (str[i] << (size - 1 - i) * 8);
+ } else {
+ total += (uint32_t) ((unsigned char) (str[i]) << (size - 1 - i) * 8);
+ }
+ }
+ return total;
+}
+
+static void smcUltostr(char* str, uint32_t val) {
+ str[0] = (char) (val >> 24);
+ str[1] = (char) (val >> 16);
+ str[2] = (char) (val >> 8);
+ str[3] = (char) val;
+ str[4] = '\0';
+}
+
+static const char* smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t* inputStructure, SmcKeyData_t* outputStructure) {
+ size_t size = sizeof(SmcKeyData_t);
+
+ if (IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size) != kIOReturnSuccess) {
+ return "IOConnectCallStructMethod(conn) failed";
+ }
+ return NULL;
+}
+
+// Provides key info, using a cache to dramatically improve the energy impact of smcFanControl
+static const char* smcGetKeyInfo(io_connect_t conn, const uint32_t key, SmcKeyData_keyInfo_t* key_info) {
+ SmcKeyData_t inputStructure = { 0 };
+ SmcKeyData_t outputStructure = { 0 };
+
+ inputStructure.key = key;
+ inputStructure.data8 = kSmcCmdReadKeyInfo;
+
+ const char* error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);
+ if (error) {
+ return error;
+ }
+
+ *key_info = outputStructure.keyInfo;
+ return NULL;
+}
+
+static const char* smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcVal_t* val) {
+ SmcKeyData_t inputStructure = { 0 };
+ SmcKeyData_t outputStructure = { 0 };
+
+ inputStructure.key = smcStrtoul(key, 4, 16);
+ strcpy(val->key, key);
+
+ const char* error = smcGetKeyInfo(conn, inputStructure.key, &outputStructure.keyInfo);
+ if (error) {
+ return error;
+ }
+
+ val->dataSize = outputStructure.keyInfo.dataSize;
+ smcUltostr(val->dataType, outputStructure.keyInfo.dataType);
+ inputStructure.keyInfo.dataSize = val->dataSize;
+ inputStructure.data8 = kSmcCmdReadBytes;
+
+ error = smcCall(conn, kKernelIndexSmc, &inputStructure, &outputStructure);
+ if (error) {
+ return error;
+ }
+
+ memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes));
+
+ return NULL;
+}
+
+static const char* smcOpen(io_connect_t* conn) {
+ FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMC"));
+ if (!device) {
+ return "No SMC device found";
+ }
+
+ if (IOServiceOpen(device, mach_task_self(), 0, conn) != kIOReturnSuccess) {
+ return "IOServiceOpen() failed";
+ }
+
+ return NULL;
+}
+
+static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, double* value) {
+ SmcVal_t val = { 0 };
+ const char* error = smcReadSmcVal(conn, key, &val);
+ if (error != NULL) {
+ return error;
+ }
+ if (val.dataSize == 0) {
+ return "Empty SMC result";
+ }
+
+ switch (val.dataType[0]) {
+ case 'u': // unsigned integer types
+ if (val.dataType[1] == 'i') {
+ switch (val.dataSize) {
+ case 1:
+ *value = *(uint8_t*) (val.bytes);
+ break;
+ case 2:
+ *value = ntohs(*(uint16_t*) (val.bytes));
+ break;
+ case 4:
+ *value = ntohl(*(uint32_t*) (val.bytes));
+ break;
+ case 8:
+ *value = (double) ntohll(*(uint64_t*) (val.bytes));
+ break;
+ default:
+ return "Unsupported SMC unsigned integer data size";
+ }
+ } else {
+ return "Unsupported SMC unsigned data type";
+ }
+ break;
+
+ case 'f': // floating point types
+ if (ffStrEquals(val.dataType, "flt ") && val.dataSize == 4) {
+ *value = *(float*) (val.bytes);
+ } else if (val.dataType[1] == 'p' && val.dataSize == 2) // fixed point types
+ {
+ if (ffStrEquals(val.dataType, "fp1f")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 32768.0;
+ } else if (ffStrEquals(val.dataType, "fp4c")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 4096.0;
+ } else if (ffStrEquals(val.dataType, "fp5b")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 2048.0;
+ } else if (ffStrEquals(val.dataType, "fp6a")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 1024.0;
+ } else if (ffStrEquals(val.dataType, "fp79")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 512.0;
+ } else if (ffStrEquals(val.dataType, "fp88")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 256.0;
+ } else if (ffStrEquals(val.dataType, "fpa6")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 64.0;
+ } else if (ffStrEquals(val.dataType, "fpc4")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 16.0;
+ } else if (ffStrEquals(val.dataType, "fpe2")) {
+ *value = ntohs(*(uint16_t*) (val.bytes)) / 4.0;
+ } else {
+ return "Unsupported SMC floating point data type";
+ }
+ } else {
+ return "Unsupported SMC floating point data type";
+ }
+ break;
+
+ case 's': // signed integer types
+ if (val.dataType[1] == 'i') {
+ switch (val.dataSize) {
+ case 1:
+ *value = *(int8_t*) (val.bytes);
+ break;
+ case 2:
+ *value = ntohs(*(int16_t*) (val.bytes));
+ break;
+ case 4:
+ *value = ntohl(*(int32_t*) (val.bytes));
+ break;
+ case 8:
+ *value = (double) ntohll(*(int64_t*) (val.bytes));
+ break;
+ default:
+ return "Unsupported SMC signed integer data size";
+ }
+ } else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types
+ {
+ if (ffStrEquals(val.dataType, "sp1e")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 16384.0;
+ } else if (ffStrEquals(val.dataType, "sp3c")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 4096.0;
+ } else if (ffStrEquals(val.dataType, "sp4b")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 2048.0;
+ } else if (ffStrEquals(val.dataType, "sp5a")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1024.0;
+ } else if (ffStrEquals(val.dataType, "sp69")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 512.0;
+ } else if (ffStrEquals(val.dataType, "sp78")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 256.0;
+ } else if (ffStrEquals(val.dataType, "sp87")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 128.0;
+ } else if (ffStrEquals(val.dataType, "sp96")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 64.0;
+ } else if (ffStrEquals(val.dataType, "spb4")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 16.0;
+ } else if (ffStrEquals(val.dataType, "spf0")) {
+ *value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1.0;
+ } else {
+ return "Unsupported SMC signed integer data type";
+ }
+ } else {
+ return "Unsupported SMC signed data type";
+ }
+ break;
+
+ case '{': // special types like pwm
+ if (ffStrEquals(val.dataType, "{pwm") && val.dataSize == 2) {
+ *value = (double) ntohs(*(uint16_t*) (val.bytes)) * 100 / 65536.0;
+ } else {
+ return "Unsupported SMC special data type";
+ }
+ break;
+
+ default:
+ return "Unsupported SMC data type";
+ }
+ return NULL;
+}
+
+static bool detectTemp(io_connect_t conn, const char* sensor, double* sum) {
+ double temp = 0;
+ const char* error = smcReadValue(conn, sensor, &temp);
+ if (error) {
+ return false;
+ }
+ // https://github.com/exelban/stats/blob/14e29c4d60229c363cca9c9d25c30c87b7870830/Modules/Sensors/readers.swift#L124
+ if (temp < 10 || temp > 120) {
+ return false;
+ }
+ *sum += temp;
+ return true;
+}
+
+static io_connect_t conn;
+
+const char* ffDetectSmcSpecificTemp(const char* sensor, double* result) {
+ if (!conn) {
+ if (smcOpen(&conn) != NULL) {
+ conn = (io_connect_t) -1;
+ }
+ }
+ if (conn == (io_connect_t) -1) {
+ return "Could not open SMC connection";
+ }
+
+ if (!detectTemp(conn, sensor, result)) {
+ return "Could not read SMC temperature";
+ }
+
+ return NULL;
+}
+
+const char* ffDetectSmcTemps(enum FFTempType type, double* result) {
+ if (!conn) {
+ if (smcOpen(&conn) != NULL) {
+ conn = (io_connect_t) -1;
+ }
+ }
+ if (conn == (io_connect_t) -1) {
+ return "Could not open SMC connection";
+ }
+
+ uint32_t count = 0;
+ *result = 0;
+
+ // https://github.com/exelban/stats/blob/master/Modules/Sensors/values.swift
+ switch (type) {
+ case FF_TEMP_CPU_X64:
+ count += detectTemp(conn, "TC0D", result); // CPU diode
+ count += detectTemp(conn, "TC0E", result); // CPU diode virtual
+ count += detectTemp(conn, "TC0F", result); // CPU diode filtered
+ count += detectTemp(conn, "TC0P", result); // CPU proximity
+ break;
+
+ case FF_TEMP_CPU_M1X:
+ count += detectTemp(conn, "Tp09", result); // CPU efficient core 1
+ count += detectTemp(conn, "Tp0T", result); // CPU efficient core 2
+
+ count += detectTemp(conn, "Tp01", result); // CPU performance core 1
+ count += detectTemp(conn, "Tp05", result); // CPU performance core 2
+ count += detectTemp(conn, "Tp0D", result); // CPU performance core 3
+ count += detectTemp(conn, "Tp0H", result); // CPU performance core 4
+ count += detectTemp(conn, "Tp0L", result); // CPU performance core 5
+ count += detectTemp(conn, "Tp0P", result); // CPU performance core 6
+ count += detectTemp(conn, "Tp0X", result); // CPU performance core 7
+ count += detectTemp(conn, "Tp0b", result); // CPU performance core 8
+ break;
+
+ case FF_TEMP_CPU_M2X:
+ count += detectTemp(conn, "Tp1h", result); // CPU efficiency core 1
+ count += detectTemp(conn, "Tp1t", result); // CPU efficiency core 2
+ count += detectTemp(conn, "Tp1p", result); // CPU efficiency core 3
+ count += detectTemp(conn, "Tp1l", result); // CPU efficiency core 4
+
+ count += detectTemp(conn, "Tp01", result); // CPU performance core 1
+ count += detectTemp(conn, "Tp05", result); // CPU performance core 2
+ count += detectTemp(conn, "Tp09", result); // CPU performance core 3
+ count += detectTemp(conn, "Tp0D", result); // CPU performance core 4
+ count += detectTemp(conn, "Tp0X", result); // CPU performance core 5
+ count += detectTemp(conn, "Tp0b", result); // CPU performance core 6
+ count += detectTemp(conn, "Tp0f", result); // CPU performance core 7
+ count += detectTemp(conn, "Tp0j", result); // CPU performance core 8
+ break;
+
+ case FF_TEMP_CPU_M3X:
+ count += detectTemp(conn, "Te05", result); // CPU efficiency core 1
+ count += detectTemp(conn, "Te0L", result); // CPU efficiency core 2
+ count += detectTemp(conn, "Te0P", result); // CPU efficiency core 3
+ count += detectTemp(conn, "Te0S", result); // CPU efficiency core 4
+ count += detectTemp(conn, "Tf04", result); // CPU performance core 1
+ count += detectTemp(conn, "Tf09", result); // CPU performance core 2
+ count += detectTemp(conn, "Tf0A", result); // CPU performance core 3
+ count += detectTemp(conn, "Tf0B", result); // CPU performance core 4
+ count += detectTemp(conn, "Tf0D", result); // CPU performance core 5
+ count += detectTemp(conn, "Tf0E", result); // CPU performance core 6
+ count += detectTemp(conn, "Tf44", result); // CPU performance core 7
+ count += detectTemp(conn, "Tf49", result); // CPU performance core 8
+ count += detectTemp(conn, "Tf4A", result); // CPU performance core 9
+ count += detectTemp(conn, "Tf4B", result); // CPU performance core 10
+ count += detectTemp(conn, "Tf4D", result); // CPU performance core 11
+ count += detectTemp(conn, "Tf4E", result); // CPU performance core 12
+ break;
+
+ case FF_TEMP_CPU_M4X:
+ count += detectTemp(conn, "Te05", result); // CPU efficiency core 1
+ count += detectTemp(conn, "Te0S", result); // CPU efficiency core 2
+ count += detectTemp(conn, "Te09", result); // CPU efficiency core 3
+ count += detectTemp(conn, "Te0H", result); // CPU efficiency core 4
+ count += detectTemp(conn, "Tp01", result); // CPU performance core 1
+ count += detectTemp(conn, "Tp05", result); // CPU performance core 2
+ count += detectTemp(conn, "Tp09", result); // CPU performance core 3
+ count += detectTemp(conn, "Tp0D", result); // CPU performance core 4
+ count += detectTemp(conn, "Tp0V", result); // CPU performance core 5
+ count += detectTemp(conn, "Tp0Y", result); // CPU performance core 6
+ count += detectTemp(conn, "Tp0b", result); // CPU performance core 7
+ count += detectTemp(conn, "Tp0e", result); // CPU performance core 8
+ break;
+
+ case FF_TEMP_GPU_INTEL:
+ count += detectTemp(conn, "TCGC", result); // GPU Intel Graphics
+ goto gpu_unknown;
+
+ case FF_TEMP_GPU_AMD:
+ count += detectTemp(conn, "TGDD", result); // GPU AMD Radeon
+ goto gpu_unknown;
+
+ case FF_TEMP_GPU_UNKNOWN: // Nvidia?
+ gpu_unknown:
+ count += detectTemp(conn, "TG0D", result); // GPU diode
+ count += detectTemp(conn, "TG0P", result); // GPU proximity
+ break;
+
+ case FF_TEMP_GPU_M1X:
+ count += detectTemp(conn, "Tg05", result); // GPU 1
+ count += detectTemp(conn, "Tg0D", result); // GPU 2
+ count += detectTemp(conn, "Tg0L", result); // GPU 3
+ count += detectTemp(conn, "Tg0T", result); // GPU 4
+ break;
+
+ case FF_TEMP_GPU_M2X:
+ count += detectTemp(conn, "Tg0f", result); // GPU 1
+ count += detectTemp(conn, "Tg0j", result); // GPU 2
+ break;
+
+ case FF_TEMP_GPU_M3X:
+ count += detectTemp(conn, "Tf14", result); // GPU 1
+ count += detectTemp(conn, "Tf18", result); // GPU 2
+ count += detectTemp(conn, "Tf19", result); // GPU 3
+ count += detectTemp(conn, "Tf1A", result); // GPU 4
+ count += detectTemp(conn, "Tf24", result); // GPU 5
+ count += detectTemp(conn, "Tf28", result); // GPU 6
+ count += detectTemp(conn, "Tf29", result); // GPU 7
+ count += detectTemp(conn, "Tf2A", result); // GPU 8
+ break;
+
+ case FF_TEMP_GPU_M4X:
+ count += detectTemp(conn, "Tg0G", result); // GPU 1 (Basic)
+ count += detectTemp(conn, "Tg0H", result); // GPU 2 (Basic)
+ count += detectTemp(conn, "Tg1U", result); // GPU 1 (Pro / Max)
+ count += detectTemp(conn, "Tg1k", result); // GPU 2 (Pro / Max)
+ count += detectTemp(conn, "Tg0K", result); // GPU 3
+ count += detectTemp(conn, "Tg0L", result); // GPU 4
+ count += detectTemp(conn, "Tg0d", result); // GPU 5
+ count += detectTemp(conn, "Tg0e", result); // GPU 6
+ count += detectTemp(conn, "Tg0j", result); // GPU 7
+ count += detectTemp(conn, "Tg0k", result); // GPU 8
+ break;
+
+ case FF_TEMP_BATTERY:
+ count += detectTemp(conn, "TB1T", result); // Battery
+ count += detectTemp(conn, "TB2T", result); // Battery
+ break;
+
+ case FF_TEMP_MEMORY:
+ count += detectTemp(conn, "Tm02", result); // Memory 1
+ count += detectTemp(conn, "Tm06", result); // Memory 2
+ count += detectTemp(conn, "Tm08", result); // Memory 3
+ count += detectTemp(conn, "Tm09", result); // Memory 4
+ break;
+ }
+
+ if (count == 0) {
+ return "No temperatures detected";
+ }
+
+ *result /= count;
+
+ return NULL;
+}
diff --git a/src/common/apple/smc_temps.h b/src/common/apple/smc_temps.h
new file mode 100644
index 0000000..6783bb9
--- /dev/null
+++ b/src/common/apple/smc_temps.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include "fastfetch.h"
+
+typedef struct FFTempValue {
+ FFstrbuf name;
+ FFstrbuf deviceClass;
+ double value;
+} FFTempValue;
+
+enum FFTempType {
+ FF_TEMP_CPU_X64,
+ FF_TEMP_CPU_M1X,
+ FF_TEMP_CPU_M2X,
+ FF_TEMP_CPU_M3X,
+ FF_TEMP_CPU_M4X,
+
+ FF_TEMP_GPU_INTEL,
+ FF_TEMP_GPU_AMD,
+ FF_TEMP_GPU_UNKNOWN,
+ FF_TEMP_GPU_M1X,
+ FF_TEMP_GPU_M2X,
+ FF_TEMP_GPU_M3X,
+ FF_TEMP_GPU_M4X,
+
+ FF_TEMP_BATTERY,
+
+ FF_TEMP_MEMORY,
+};
+
+const char* ffDetectSmcSpecificTemp(const char* sensor, double* result);
+const char* ffDetectSmcTemps(enum FFTempType type, double* result);
diff --git a/src/common/apple/version.h b/src/common/apple/version.h
new file mode 100644
index 0000000..d15acb4
--- /dev/null
+++ b/src/common/apple/version.h
@@ -0,0 +1,5 @@
+#pragma once
+
+#include "fastfetch.h"
+
+bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* retVersion);
diff --git a/src/common/apple/version.m b/src/common/apple/version.m
new file mode 100644
index 0000000..133ec5b
--- /dev/null
+++ b/src/common/apple/version.m
@@ -0,0 +1,43 @@
+#include "common/apple/version.h"
+#include "common/strutil.h"
+
+#import <Foundation/Foundation.h>
+
+#define APP_CONTENTS_MACOS ".app/Contents/MacOS"
+
+bool ffGetAppNameAndVersion(const char* exePath, FFstrbuf* retName, FFstrbuf* retVersion) {
+ char* lastSlash = strrchr(exePath, '/');
+ if (!lastSlash) {
+ return false;
+ }
+ if ((size_t) (lastSlash - exePath) > strlen("X" APP_CONTENTS_MACOS) && memcmp(lastSlash - strlen(APP_CONTENTS_MACOS), APP_CONTENTS_MACOS, strlen(APP_CONTENTS_MACOS)) != 0) {
+ return false;
+ }
+
+ lastSlash -= strlen("MacOS");
+ char infoPlistPath[PATH_MAX];
+ memcpy(infoPlistPath, exePath, lastSlash - exePath);
+ memcpy(infoPlistPath + (lastSlash - exePath), "Info.plist", sizeof("Info.plist")); // X.app/Contents/Info.plist
+ NSError* error;
+ NSDictionary* dict = [NSDictionary dictionaryWithContentsOfURL:[NSURL fileURLWithPath:@(infoPlistPath)]
+ error:&error];
+ if (!dict) {
+ return false;
+ }
+
+ if (retName) {
+ NSString* bundleName = dict[@"CFBundleDisplayName"] ?: dict[@"CFBundleName"];
+ if (bundleName) {
+ ffStrbufSetS(retName, bundleName.UTF8String);
+ }
+ }
+
+ if (retVersion) {
+ NSString* bundleVersion = dict[@"CFBundleShortVersionString"];
+ if (bundleVersion) {
+ ffStrbufSetS(retVersion, bundleVersion.UTF8String);
+ }
+ }
+
+ return true;
+}