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}