master ohmu/fastfetch-openbsd / src / detection / brightness / brightness_apple.c
ohmu  ·  2026-10-15
  1#include "brightness.h"
  2#include "detection/displayserver/displayserver.h"
  3#include "common/apple/cf_helpers.h"
  4#include "common/debug.h"
  5#include "common/edidHelper.h"
  6#include "common/time.h"
  7
  8#include <CoreGraphics/CoreGraphics.h>
  9
 10// DDC/CI
 11#ifdef __aarch64__
 12typedef CFTypeRef IOAVServiceRef;
 13[[clang::weak_import]] extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator);
 14[[clang::weak_import]] extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service);
 15[[clang::weak_import]] extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2);
 16[[clang::weak_import]] extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize);
 17[[clang::weak_import]] extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize);
 18#else
 19    // DDC/CI (Intel)
 20    #include <IOKit/IOKitLib.h>
 21    #include <IOKit/graphics/IOGraphicsLib.h>
 22    #include <IOKit/i2c/IOI2CInterface.h>
 23[[clang::weak_import]] extern void CGSServiceForDisplayNumber(CGDirectDisplayID display, io_service_t* service);
 24#endif
 25
 26// ACPI
 27[[clang::weak_import]] extern int DisplayServicesGetBrightness(CGDirectDisplayID display, float* brightness);
 28
 29// Works for internal display
 30static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) {
 31    if (DisplayServicesGetBrightness == nullptr) {
 32        FF_DEBUG("DisplayServices function DisplayServicesGetBrightness is not available");
 33        return "DisplayServices function DisplayServicesGetBrightness is not available";
 34    }
 35
 36    FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) {
 37        if (display->type == FF_DISPLAY_TYPE_BUILTIN || display->type == FF_DISPLAY_TYPE_UNKNOWN) {
 38            float value;
 39            if (DisplayServicesGetBrightness((CGDirectDisplayID) display->id, &value) == kCGErrorSuccess) {
 40                FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
 41                brightness->current = value;
 42                brightness->max = 1;
 43                brightness->min = 0;
 44                ffStrbufInitCopy(&brightness->name, &display->name);
 45                brightness->builtin = true;
 46            }
 47        }
 48    }
 49
 50    return nullptr;
 51}
 52
 53#ifdef __aarch64__
 54// https://github.com/waydabber/m1ddc
 55// Works for Apple Silicon and USB-C adapter connection ( but not HTMI )
 56static const char* detectWithDdcci([[maybe_unused]] const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
 57    if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) {
 58        FF_DEBUG("IOAVService is not available");
 59        return "IOAVService is not available";
 60    }
 61
 62    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
 63    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) {
 64        FF_DEBUG("IOServiceGetMatchingServices() failed");
 65        return "IOServiceGetMatchingServices() failed";
 66    }
 67
 68    io_registry_entry_t registryEntry;
 69    while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
 70        FF_CFTYPE_AUTO_RELEASE IOAVServiceRef service = nullptr;
 71        {
 72            FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryAv = registryEntry;
 73
 74            FF_CFTYPE_AUTO_RELEASE CFBooleanRef IOAVServiceUserInterfaceSupported = IORegistryEntryCreateCFProperty(entryAv, CFSTR("IOAVServiceUserInterfaceSupported"), kCFAllocatorDefault, kNilOptions);
 75            if (IOAVServiceUserInterfaceSupported && !CFBooleanGetValue(IOAVServiceUserInterfaceSupported)) {
 76                // IOAVServiceCreateWithService won't work
 77                continue;
 78            }
 79
 80            FF_CFTYPE_AUTO_RELEASE CFStringRef location = IORegistryEntryCreateCFProperty(entryAv, CFSTR("Location"), kCFAllocatorDefault, kNilOptions);
 81            if (location && CFStringCompare(location, CFSTR("Embedded"), 0) == 0) {
 82                // Builtin display should be handled by DisplayServices
 83                continue;
 84            }
 85
 86            service = IOAVServiceCreateWithService(kCFAllocatorDefault, (io_service_t) registryEntry);
 87            if (!service) {
 88                continue;
 89            }
 90        }
 91
 92        {
 93            uint8_t i2cIn[4] = { FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE };
 94            i2cIn[3] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2];
 95
 96            for (uint32_t i = 0; i < 2; ++i) {
 97                IOAVServiceWriteI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cIn, ARRAY_SIZE(i2cIn));
 98                ffTimeSleep(options->ddcciSleep);
 99            }
100        }
101
102        uint8_t i2cOut[12] = {};
103        if (IOAVServiceReadI2C(service, FF_DDC_CI_ADDR, FF_DDC_CI_VCP_COMMAND, i2cOut, ARRAY_SIZE(i2cOut)) == KERN_SUCCESS) {
104            if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
105                continue;
106            }
107
108            uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
109            uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
110
111            FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
112            brightness->max = max;
113            brightness->min = 0;
114            brightness->current = current;
115            ffStrbufInit(&brightness->name);
116            brightness->builtin = false;
117
118            uint8_t edid[128] = {};
119            if (IOAVServiceReadI2C(service, 0x50, 0x00, edid, ARRAY_SIZE(edid)) == KERN_SUCCESS) {
120                ffEdidGetName(edid, &brightness->name);
121            }
122        }
123    }
124
125    return nullptr;
126}
127#else
128static IOOptionBits getSupportedTransactionType(void) {
129    FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
130
131    if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceNameMatching("IOFramebufferI2CInterface"), &iterator) != KERN_SUCCESS) {
132        return kIOI2CNoTransactionType;
133    }
134
135    io_registry_entry_t registryEntry;
136    while ((registryEntry = IOIteratorNext(iterator)) != MACH_PORT_NULL) {
137        FF_IOOBJECT_AUTO_RELEASE io_service_t io_service = registryEntry;
138        FF_CFTYPE_AUTO_RELEASE CFNumberRef IOI2CTransactionTypes = IORegistryEntryCreateCFProperty(io_service, CFSTR(kIOI2CTransactionTypesKey), kCFAllocatorDefault, kNilOptions);
139
140        if (IOI2CTransactionTypes) {
141            int64_t types = 0;
142            ffCfNumGetInt64(IOI2CTransactionTypes, &types);
143
144            if (types) {
145                if ((1 << kIOI2CDDCciReplyTransactionType) & (uint64_t) types) {
146                    return kIOI2CDDCciReplyTransactionType;
147                }
148                if ((1 << kIOI2CSimpleTransactionType) & (uint64_t) types) {
149                    return kIOI2CSimpleTransactionType;
150                }
151            }
152        }
153        break;
154    }
155
156    return kIOI2CNoTransactionType;
157}
158
159static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
160    if (!CGSServiceForDisplayNumber) {
161        FF_DEBUG("CGSServiceForDisplayNumber is not available");
162        return "CGSServiceForDisplayNumber is not available";
163    }
164    IOOptionBits transactionType = getSupportedTransactionType();
165    if (transactionType == kIOI2CNoTransactionType) {
166        FF_DEBUG("No supported IOI2C transaction type found");
167        return "No supported IOI2C transaction type found";
168    }
169
170    FF_LIST_FOR_EACH (FFDisplayResult, display, displayServer->displays) {
171        if (display->type == FF_DISPLAY_TYPE_EXTERNAL) {
172            FF_IOOBJECT_AUTO_RELEASE io_service_t framebuffer = IO_OBJECT_NULL;
173            CGSServiceForDisplayNumber((CGDirectDisplayID) display->id, &framebuffer);
174            if (framebuffer == IO_OBJECT_NULL) {
175                continue;
176            }
177
178            IOItemCount count;
179            if (IOFBGetI2CInterfaceCount(framebuffer, &count) != KERN_SUCCESS || count == 0) {
180                continue;
181            }
182
183            for (IOItemCount bus = 0; bus < count; ++bus) {
184                FF_IOOBJECT_AUTO_RELEASE io_service_t interface = IO_OBJECT_NULL;
185                if (IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface) != KERN_SUCCESS) {
186                    continue;
187                }
188
189                uint8_t i2cOut[12] = {};
190                IOI2CConnectRef connect = nullptr;
191                if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) != KERN_SUCCESS) {
192                    continue;
193                }
194
195                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 };
196                i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3];
197
198                IOI2CRequest request = {
199                    .commFlags = kNilOptions,
200                    .sendAddress = FF_DDC_CI_WRITE_ADDR,
201                    .sendTransactionType = kIOI2CSimpleTransactionType,
202                    .sendBuffer = (vm_address_t) i2cIn,
203                    .sendBytes = ARRAY_SIZE(i2cIn),
204                    .minReplyDelay = options->ddcciSleep * 1000ULL,
205                    .replyAddress = FF_DDC_CI_READ_ADDR,
206                    .replySubAddress = FF_DDC_CI_VCP_COMMAND,
207                    .replyTransactionType = transactionType,
208                    .replyBytes = ARRAY_SIZE(i2cOut),
209                    .replyBuffer = (vm_address_t) i2cOut,
210                };
211                IOReturn ret = IOI2CSendRequest(connect, kNilOptions, &request);
212                IOI2CInterfaceClose(connect, kNilOptions);
213
214                if (ret != KERN_SUCCESS || request.result != kIOReturnSuccess || request.replyBytes < 10) {
215                    continue;
216                }
217                if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
218                    continue;
219                }
220
221                uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
222                uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
223
224                FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
225                brightness->max = max;
226                brightness->min = 0;
227                brightness->current = current;
228                ffStrbufInitCopy(&brightness->name, &display->name);
229                brightness->builtin = false;
230
231                break;
232            }
233        }
234    }
235
236    return nullptr;
237}
238#endif
239
240const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result) {
241    const FFDisplayServerResult* displayServer = ffConnectDisplayServer();
242
243    detectWithDisplayServices(displayServer, result);
244
245    if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && displayServer->displays.length > result->length) {
246        detectWithDdcci(displayServer, options, result);
247    }
248
249    return nullptr;
250}