For developers

What a Mac app has to get right to talk DDC/CI

DDC/CI has a reputation for being unreliable. Some of that is the hardware. Some of it is what happens when an app treats a two-wire bus like a function call.

The address, and why EDID reads are shallow

DDC/CI lives at I2C chip 0x37, sub-address 0x51. EDID answers at 0x50. The EDID offset register is one byte, so only the base block and the first extension block are reachable — anything deeper has to be declined rather than guessed at.

A monitor needs a beat

Monitors need a moment before the reply is composed. Send a request and read the answer back in the same breath and you will get nothing on a surprising number of panels.

One request, not two

The write and the read share a single request so the driver keeps the bus timing. Splitting them into two transactions looks tidier in code and behaves worse on real hardware.

Do not spend ten timeouts on a panel that ignores you

Probing every known VCP feature code sounds thorough. It also means a panel that ignores DDC/CI entirely costs ten timeouts before you learn what a brightness read would have told you immediately. Brightness gates the sweep.

Never hard-link a symbol your Mac may not have

The Intel path needs a user-space I2C API that the IOKit umbrella does not export. Referencing that symbol directly would fail inside dyld and the app would never launch at all — on every Mac without it. Resolving it with dlsym turns that into a graceful degradation to “no DDC” instead of a crash before the first window.

Two buses, one interface

Intel Macs reach DDC through an IOFramebuffer's I2C interfaces; Apple Silicon goes through the AV service. The requests on the Intel path are synchronous, so no locking is needed — but the code above that must not assume that stays true.

Honest limits

Related