waydabber / m1ddc

This little tool controls external displays (connected via USB-C/DisplayPort Alt Mode) using DDC/CI on Apple Silicon Macs. Useful to embed in various scripts.

Repository from Github https://github.comwaydabber/m1ddcRepository from Github https://github.comwaydabber/m1ddc

C Library that can be incorporated into other apps

krackers opened this issue · comments

Thanks for the logic. I wanted to incorporate this as part of another app, so I removed all the CLI parsing parts from the monolithic main function to get something more library like. I think I also made sure to handle iokit free... Maybe this can be useful to someone.

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>



typedef CFTypeRef IOAVServiceRef;
extern IOAVServiceRef IOAVServiceCreate(CFAllocatorRef allocator);
extern IOAVServiceRef IOAVServiceCreateWithService(CFAllocatorRef allocator, io_service_t service);
extern IOReturn IOAVServiceReadI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t offset, void* outputBuffer, uint32_t outputBufferSize);
extern IOReturn IOAVServiceWriteI2C(IOAVServiceRef service, uint32_t chipAddress, uint32_t dataAddress, void* inputBuffer, uint32_t inputBufferSize);

#define LUMINANCE 0x10
#define CONTRAST 0x12
#define VOLUME 0x62
#define MUTE 0x8D
#define INPUT 0x60
#define STANDBY 0xD6
#define RED 0x16 // VCP Code - Video Gain (Drive): Red
#define GREEN 0x18 // VCP Code - Video Gain (Drive): Green
#define BLUE 0x1A // VCP Code - Video Gain (Drive): Blue

#define MUTE_ON 1
#define MUTE_OFF 2

#define DDC_WAIT 10000 // depending on display this must be set to as high as 50000
#define DDC_ITERATIONS 2 // depending on display this must be set higher

void free_ioobject(io_object_t *value) { if (*value != NULL) {IOObjectRelease(*value);} }
void free_cftype(CFTypeRef *value) { if (*value != NULL) {CFRelease(*value);} }

NSArray* getDisplayList() {
    NSMutableArray *ret = [NSMutableArray array];

    __attribute__((cleanup(free_ioobject))) io_iterator_t iter;
    __attribute__((cleanup(free_ioobject))) io_service_t service = 0;
    __attribute__((cleanup(free_ioobject))) io_registry_entry_t root = IORegistryGetRootEntry(kIOMainPortDefault);
    kern_return_t kerr = IORegistryEntryCreateIterator(root, "IOService", kIORegistryIterateRecursively, &iter);
    
    if (kerr != KERN_SUCCESS) {
        return NULL;
    }

    int i=1;
    
    while ((service = IOIteratorNext(iter)) != MACH_PORT_NULL) {
        io_name_t name;
        IORegistryEntryGetName(service, name);
        if ( !strcmp(name, "AppleCLCD2") ) {
            
            NSString *edidUUID =  (__bridge_transfer NSString*) IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR("EDID UUID"), kCFAllocatorDefault, kIORegistryIterateRecursively);
            
            if ( !(edidUUID == NULL) ) {
                CFDictionaryRef displayAttrs = (CFDictionaryRef) IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR("DisplayAttributes"), kCFAllocatorDefault, kIORegistryIterateRecursively);
                
                if (displayAttrs ) {
                    NSDictionary* displayAttrsNS = (__bridge NSDictionary*) displayAttrs;
                    NSDictionary* productAttrs = [displayAttrsNS objectForKey:@"ProductAttributes"];
                    if (productAttrs) {
                        [ret addObject: [NSString stringWithFormat:@"%d %@ %@", i, [productAttrs objectForKey:@"ProductName"], edidUUID]];
                    }
                    CFRelease(displayAttrs);
                }
                i++;
            }
        }
    }
    return ret;
}



IOAVServiceRef getAvService(int display) {
    
    if (display == 0) {
        return IOAVServiceCreate(kCFAllocatorDefault);
    }
    
    IOAVServiceRef ret;
    __attribute__((cleanup(free_ioobject))) io_iterator_t iter;
    __attribute__((cleanup(free_ioobject))) io_service_t service = 0;
    __attribute__((cleanup(free_ioobject))) io_registry_entry_t root = IORegistryGetRootEntry(kIOMainPortDefault);
    kern_return_t kerr = IORegistryEntryCreateIterator(root, "IOService", kIORegistryIterateRecursively, &iter);
    
    if (kerr != KERN_SUCCESS) {
        return NULL;
    }
    
    int i=1;
    bool noMatch = true;
    
    while ((service = IOIteratorNext(iter)) != MACH_PORT_NULL) {
        io_name_t name;
        IORegistryEntryGetName(service, name);
        if ( !strcmp(name, "AppleCLCD2") ) {
            
            CFStringRef edidUUID = (CFStringRef) IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR("EDID UUID"), kCFAllocatorDefault, kIORegistryIterateRecursively);
            
            if ( !(edidUUID == NULL) ) {
                if ( display == i ) {
                    while ((service = IOIteratorNext(iter)) != MACH_PORT_NULL) {
                        io_name_t name;
                        IORegistryEntryGetName(service, name);
                        if ( !strcmp(name, "DCPAVServiceProxy") ) {
                            
                            ret = IOAVServiceCreateWithService(kCFAllocatorDefault, service);
                            CFStringRef location = (CFStringRef) IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR("Location"), kCFAllocatorDefault, kIORegistryIterateRecursively);
                            
                            if ( !( location == NULL || !ret || CFStringCompare(CFSTR("External"), location, 0) ) ) {
                                noMatch = false;
                                break;
                            }
                            CFRelease(location);
                        }
                    }
                }
                
                i++;
                CFRelease(edidUUID);
                
            }
        }
    }
     if ( noMatch ) {
        printf("The specified display does not exist. Use 'display list' to list displays and use it's number (1, 2...) to specify display!\n");
        return NULL;
    }
    return ret;
}

IOReturn readDDC(IOAVServiceRef avService, UInt8 action, signed char *curValue, signed char *maxValue) {
    // Get ready for DDC operations
    
    UInt8 data[256];
    memset(data, 0, sizeof(data));
    data[2] = action;

    *curValue=-1;
    *maxValue=-1;
    
    data[0] = 0x82;
    data[1] = 0x01;
    data[3] = 0x6e ^ data[0] ^ data[1] ^ data[2] ^ data[3];
    
    IOReturn err;
    for (int i = 0; i < DDC_ITERATIONS; ++i) {
        
        usleep(DDC_WAIT);
        err = IOAVServiceWriteI2C(avService, 0x37, 0x51, data, 4);
        
        if (err) {
            NSLog(@"%@", [NSString stringWithFormat:@"I2C communication failure: %s\n", mach_error_string(err)]);
            return err;
        }
        
    }
    
    char i2cBytes[12];
    memset(i2cBytes, 0, sizeof(i2cBytes));
    
    usleep(DDC_WAIT);
    err = IOAVServiceReadI2C(avService, 0x37, 0x51, i2cBytes, 12);
    
    if (err) {
        NSLog(@"%@", [NSString stringWithFormat:@"I2C communication failure: %s\n", mach_error_string(err)]);
        return err;
    }
    
    NSData *readData = [NSData dataWithBytes:(const void *)i2cBytes length:(NSUInteger)11];
    
    NSRange maxValueRange = {7, 1};
    NSRange currentValueRange = {9, 1};
    
    [[readData subdataWithRange:maxValueRange] getBytes:maxValue length:sizeof(1)];
    [[readData subdataWithRange:currentValueRange] getBytes:curValue length:sizeof(1)];
    return 0;
}

IOReturn setDDC( IOAVServiceRef avService, UInt8 action, int setValue) {
    UInt8 data[256];

    
    data[0] = 0x84;
    data[1] = 0x03;
    data[2] = action;
    data[3] = (setValue) >> 8;
    data[4] = setValue & 255;
    data[5] = 0x6E ^ 0x51 ^ data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4];
    
    IOReturn err;
    for (int i = 0; i <= DDC_ITERATIONS; i++) {
        
        usleep(DDC_WAIT);
        err = IOAVServiceWriteI2C(avService, 0x37, 0x51, data, 6);
        
        if (err) {
            NSLog(@"%@", [NSString stringWithFormat:@"I2C communication failure: %s\n", mach_error_string(err)]);
            return err;
            
        }
        
    }
    return 0;
}

int main(int argc, char** argv) {
    NSLog(@"%@", getDisplayList());
    IOAVServiceRef ref = getAvService(1);
    signed char current = 0;
    signed char max = 0;
//  readDDC(ref, VOLUME, &current, &max);
//  printf("Current %d %d\n", current, max);
//  setDDC(ref, VOLUME, 10);
    readDDC(ref, VOLUME, &current, &max);
    printf("Current %d %d\n", current, max);
    CFRelease(ref);
}

Great, thank you! :)