Skip to content

Instantly share code, notes, and snippets.

@tao-j
Last active September 2, 2023 02:17
Show Gist options
  • Save tao-j/c7a3c1ec7c2f59ebe45d161d620d9169 to your computer and use it in GitHub Desktop.
Save tao-j/c7a3c1ec7c2f59ebe45d161d620d9169 to your computer and use it in GitHub Desktop.
M1 External Monitor Brightness Control over DDC
@import Darwin;
@import Foundation;
@import IOKit;
// clang -fmodules -o brt brt.m && ./brt
// credit to @zhuowei for discovering the following APIs
typedef CFTypeRef IOAVServiceRef;
extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator);
extern IOReturn IOAVServiceCopyEDID(IOAVServiceRef service, CFDataRef* x2);
// outputBufferSize must be less than (1 << 12) (4096 bytes)
extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer,
uint32_t outputBufferSize);
// This didn't work for me (returned no error, but EDID not written)
extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer,
uint32_t inputBufferSize);
#define BRIGHTNESS 0x10
#define CONTRAST 0x12
#define AUDIO_SPEAKER_VOLUME 0x62
#define AUDIO_MUTE 0x8D
struct DDCWriteCommand
{
UInt8 control_id;
UInt8 new_value;
};
int main(int argc, char** argv) {
IOAVServiceRef avService = IOAVServiceCreate(kCFAllocatorDefault);
if (!avService) {
NSLog(@"Can't open IOAVService: do you have an external monitor? Are you on M1?");
return 1;
}
char data[16];
struct DDCWriteCommand command;
command.control_id = BRIGHTNESS;
if (argc == 2) {
command.new_value = atoi(argv[1]);
}
else {
command.new_value = 1;
}
data[0] = 0x84;
data[1] = 0x03;
data[2] = command.control_id;
data[3] = (command.new_value) >> 8;
data[4] = command.new_value & 255;
data[5] = 0x6E ^ 0x51 ^ data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4];
IOReturn err = IOAVServiceWriteI2C(avService, 0x37, 0x51, data, 6);
return err;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment