summaryrefslogtreecommitdiffstats
path: root/src/detection/brightness/brightness_apple.c
diff options
context:
space:
mode:
authorsumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
committersumuel <samuel@yakubos.org>2026-08-17 20:44:55 +0000
commit76424950e373d3b04ac3dd13019151bfba3e8423 (patch)
tree4c3cfdbda039e592b9186be3e28f8d7cfd439e8a /src/detection/brightness/brightness_apple.c
Add the files
Diffstat (limited to 'src/detection/brightness/brightness_apple.c')
-rw-r--r--src/detection/brightness/brightness_apple.c244
1 files changed, 244 insertions, 0 deletions
diff --git a/src/detection/brightness/brightness_apple.c b/src/detection/brightness/brightness_apple.c
new file mode 100644
index 0000000..5dd12b2
--- /dev/null
+++ b/src/detection/brightness/brightness_apple.c
@@ -0,0 +1,244 @@
+#include "brightness.h"
+#include "detection/displayserver/displayserver.h"
+#include "common/apple/cf_helpers.h"
+#include "common/edidHelper.h"
+#include "common/time.h"
+
+#include <CoreGraphics/CoreGraphics.h>
+
+// DDC/CI
+#ifdef __aarch64__
+typedef CFTypeRef IOAVServiceRef;
+extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator) FF_A_WEAK_IMPORT;
+extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service) FF_A_WEAK_IMPORT;
+extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2) FF_A_WEAK_IMPORT;
+extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize) FF_A_WEAK_IMPORT;
+extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize) FF_A_WEAK_IMPORT;
+#else
+ // DDC/CI (Intel)
+ #include <IOKit/IOKitLib.h>
+ #include <IOKit/graphics/IOGraphicsLib.h>
+ #include <IOKit/i2c/IOI2CInterface.h>
+extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service) FF_A_WEAK_IMPORT;
+#endif
+
+// ACPI
+extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float* brightness) FF_A_WEAK_IMPORT;
+
+// Works for internal display
+static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) {
+ if (DisplayServicesGetBrightness == NULL) {
+ return "DisplayServices function DisplayServicesGetBrightness is not available";
+ }
+
+ FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) {
+ if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) {
+ float value;
+ if (DisplayServicesGetBrightness((CGDirectDisplayID) display->id, &value) == kCGErrorSuccess) {
+ FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
+ brightness->current = value;
+ brightness->max = 1;
+ brightness->min = 0;
+ ffStrbufInitCopy(&brightness->name, &display->name);
+ brightness->builtin = true;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+#ifdef __aarch64__
+// https://github.com/waydabber/m1ddc
+// Works for Apple Silicon and USB-C adapter connection ( but not HTMI )
+static const char* detectWithDdcci(FF_A_UNUSED const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
+ if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) {
+ return "IOAVService is not available";
+ }
+
+ FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
+ if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) {
+ return "IOServiceGetMatchingServices() failed";
+ }
+
+ io_registry_entry_t registryEntry;
+ while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
+ FF_CFTYPE_AUTO_RELEASE IOAVServiceRef service = NULL;
+ {
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryAv = registryEntry;
+
+ FF_CFTYPE_AUTO_RELEASE CFBooleanRef IOAVServiceUserInterfaceSupported = IORegistryEntryCreateCFProperty(entryAv, CFSTR("IOAVServiceUserInterfaceSupported"), kCFAllocatorDefault, kNilOptions);
+ if (IOAVServiceUserInterfaceSupported && !CFBooleanGetValue(IOAVServiceUserInterfaceSupported)) {
+ // IOAVServiceCreateWithService won't work
+ continue;
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFStringRef location = IORegistryEntryCreateCFProperty(entryAv, CFSTR("Location"), kCFAllocatorDefault, kNilOptions);
+ if (location && CFStringCompare(location, CFSTR("Embedded"), 0) == 0) {
+ // Builtin display should be handled by DisplayServices
+ continue;
+ }
+
+ service = IOAVServiceCreateWithService(kCFAllocatorDefault, (io_service_t) registryEntry);
+ if (!service) {
+ continue;
+ }
+ }
+
+ {
+ uint8_t i2cIn[4] = { FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE };
+ i2cIn[3] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2];
+
+ for (uint32_t i = 0; i < 2; ++i) {
+ IOAVServiceWriteI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cIn, ARRAY_SIZE(i2cIn));
+ ffTimeSleep(options->ddcciSleep);
+ }
+ }
+
+ uint8_t i2cOut[12] = {};
+ if (IOAVServiceReadI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cOut, ARRAY_SIZE(i2cOut)) == KERN_SUCCESS) {
+ if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
+ continue;
+ }
+
+ uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
+ uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
+
+ FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
+ brightness->max = max;
+ brightness->min = 0;
+ brightness->current = current;
+ ffStrbufInit(&brightness->name);
+ brightness->builtin = false;
+
+ uint8_t edid[128] = {};
+ if (IOAVServiceReadI2C(service, 0x50, 0x00, edid, ARRAY_SIZE(edid)) == KERN_SUCCESS) {
+ ffEdidGetName(edid, &brightness->name);
+ }
+ }
+ }
+
+ return NULL;
+}
+#else
+static IOOptionBits getSupportedTransactionType(void) {
+ FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
+
+ if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceNameMatching("IOFramebufferI2CInterface"), &iterator) != KERN_SUCCESS) {
+ return kIOI2CNoTransactionType;
+ }
+
+ io_registry_entry_t registryEntry;
+ while ((registryEntry = IOIteratorNext(iterator)) != MACH_PORT_NULL) {
+ FF_IOOBJECT_AUTO_RELEASE io_service_t io_service = registryEntry;
+ FF_CFTYPE_AUTO_RELEASE CFNumberRef IOI2CTransactionTypes = IORegistryEntryCreateCFProperty(io_service, CFSTR(kIOI2CTransactionTypesKey), kCFAllocatorDefault, kNilOptions);
+
+ if (IOI2CTransactionTypes) {
+ int64_t types = 0;
+ ffCfNumGetInt64(IOI2CTransactionTypes, &types);
+
+ if (types) {
+ if ((1 << kIOI2CDDCciReplyTransactionType) & (uint64_t) types) {
+ return kIOI2CDDCciReplyTransactionType;
+ }
+ if ((1 << kIOI2CSimpleTransactionType) & (uint64_t) types) {
+ return kIOI2CSimpleTransactionType;
+ }
+ }
+ }
+ break;
+ }
+
+ return kIOI2CNoTransactionType;
+}
+
+static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
+ if (!CGSServiceForDisplayNumber) {
+ return "CGSServiceForDisplayNumber is not available";
+ }
+ IOOptionBits transactionType = getSupportedTransactionType();
+ if (transactionType == kIOI2CNoTransactionType) {
+ return "No supported IOI2C transaction type found";
+ }
+
+ FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) {
+ if (display->type == FF_DISPLAY_TYPE_EXTERNAL) {
+ FF_IOOBJECT_AUTO_RELEASE io_service_t framebuffer = IO_OBJECT_NULL;
+ CGSServiceForDisplayNumber((CGDirectDisplayID) display->id, &framebuffer);
+ if (framebuffer == IO_OBJECT_NULL) {
+ continue;
+ }
+
+ IOItemCount count;
+ if (IOFBGetI2CInterfaceCount(framebuffer, &count) != KERN_SUCCESS || count == 0) {
+ continue;
+ }
+
+ for (IOItemCount bus = 0; bus < count; ++bus) {
+ FF_IOOBJECT_AUTO_RELEASE io_service_t interface = IO_OBJECT_NULL;
+ if (IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) != KERN_SUCCESS) {
+ continue;
+ }
+
+ uint8_t i2cOut[12] = {};
+ IOI2CConnectRef connect = NULL;
+ if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) != KERN_SUCCESS) {
+ continue;
+ }
+
+ uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 };
+ i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3];
+
+ IOI2CRequest request = {
+ .commFlags = kNilOptions,
+ .sendAddress = FF_DDC_CI_WRITE_ADDR,
+ .sendTransactionType = kIOI2CSimpleTransactionType,
+ .sendBuffer = (vm_address_t) i2cIn,
+ .sendBytes = ARRAY_SIZE(i2cIn),
+ .minReplyDelay = options->ddcciSleep * 1000ULL,
+ .replyAddress = FF_DDC_CI_READ_ADDR,
+ .replySubAddress = FF_DDC_CI_VCP_COMMAND,
+ .replyTransactionType = transactionType,
+ .replyBytes = ARRAY_SIZE(i2cOut),
+ .replyBuffer = (vm_address_t) i2cOut,
+ };
+ IOReturn ret = IOI2CSendRequest(connect, kNilOptions, &request);
+ IOI2CInterfaceClose(connect, kNilOptions);
+
+ if (ret != KERN_SUCCESS || request.result != kIOReturnSuccess || request.replyBytes < 10) {
+ continue;
+ }
+ if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
+ continue;
+ }
+
+ uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
+ uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
+
+ FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
+ brightness->max = max;
+ brightness->min = 0;
+ brightness->current = current;
+ ffStrbufInitCopy(&brightness->name, &display->name);
+ brightness->builtin = false;
+
+ break;
+ }
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result) {
+ const FFDisplayServerResult* displayServer = ffConnectDisplayServer();
+
+ detectWithDisplayServices(displayServer, result);
+
+ if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && displayServer->displays.length > result->length) {
+ detectWithDdcci(displayServer, options, result);
+ }
+
+ return NULL;
+}