From 8cbd0ae44fd62d8ed22be5ed2304e1a32b7ce6ab Mon Sep 17 00:00:00 2001 From: Saman-VDR Date: Tue, 13 Dec 2016 10:28:58 +0100 Subject: [PATCH 01/15] Replace deprecated CGDisplayIOServicePort with IOFramebufferPortFromCGDisplayID from https://github.com/kfix/ddcctl/blob/master/DDC.h --- BrightnessMenulet/ddc.c | 91 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index 4bb9ebd..b379d25 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -11,6 +11,94 @@ #include #include "ddc.h" +// IOFramebufferPortFromCGDisplayID from https://github.com/kfix/ddcctl/blob/master/DDC.h +#include + +static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID) +// iterate IOreg's device tree to find the IOFramebuffer mach service port that corresponds to a given CGDisplayID +// replaces CGDisplayIOServicePort: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/Quartz_Services_Ref/index.html#//apple_ref/c/func/CGDisplayIOServicePort +// based on: https://github.com/glfw/glfw/pull/192/files +{ + io_iterator_t iter; + io_service_t serv, servicePort = 0; + + kern_return_t err = IOServiceGetMatchingServices( kIOMasterPortDefault, + IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), // IOFramebufferI2CInterface + &iter); + + if (err != KERN_SUCCESS) + return 0; + + // now recurse the IOReg tree + while ((serv = IOIteratorNext(iter)) != MACH_PORT_NULL) + { + CFDictionaryRef info; + io_name_t name; + CFIndex vendorID, productID, serialNumber = 0; + CFNumberRef vendorIDRef, productIDRef, serialNumberRef; + CFStringRef location = CFSTR(""); + //CFStringRef serial = CFSTR(""); + Boolean success = 0; + + // get metadata from IOreg node + IORegistryEntryGetName(serv, name); + info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName); + + /* When assigning a display ID, Quartz considers the following parameters:Vendor, Model, Serial Number and Position in the I/O Kit registry */ + // http://opensource.apple.com//source/IOGraphics/IOGraphics-179.2/IOGraphicsFamily/IOKit/graphics/IOGraphicsTypes.h + CFStringRef locationRef = CFDictionaryGetValue(info, CFSTR(kIODisplayLocationKey)); + location = CFStringCreateCopy(NULL, locationRef); + //CFStringRef serialRef = CFDictionaryGetValue(info, CFSTR(kDisplaySerialString)); + //serial = CFStringCreateCopy(NULL, serialRef); + + if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayVendorID), (const void**)&vendorIDRef)) + success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); + + if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayProductID), (const void**)&productIDRef)) + success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); + + IOItemCount busCount; + IOFBGetI2CInterfaceCount(serv, &busCount); + + if (!success || busCount < 1) { + // this does not seem to be a DDC-enabled display, skip it + CFRelease(info); + continue; + } else { + // MacBook built-in screens have IOFBI2CInterfaceIDs=(0) but do not respond to DDC comms + // they also do not have a BusType: IOFBI2CInterfaceInfo = ({"IOI2CBusType"=1 .. }) + // if (framebuffer.hasDDCConnect(0)) // https://developer.apple.com/reference/kernel/ioframebuffer/1813510-hasddcconnect?language=objc + // kDisplayBundleKey + // kAppleDisplayTypeKey -- if this is an Apple display, can use IODisplay func to change brightness: http://stackoverflow.com/a/32691700/3878712 + } + + if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplaySerialNumber), (const void**)&serialNumberRef)) + CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); + + // compare IOreg's metadata to CGDisplay's metadata to infer if the IOReg's I2C monitor is the display for the given NSScreen.displayID + if (CGDisplayVendorNumber(displayID) != vendorID || + CGDisplayModelNumber(displayID) != productID || + CGDisplaySerialNumber(displayID) != serialNumber ) // SN is zero in lots of cases, so duplicate-monitors can confuse us :-/ + { + CFRelease(info); + continue; + } + + // considering this IOFramebuffer as the match for the CGDisplay, dump out its information +// printf("VN:%ld PN:%ld SN:%ld", vendorID, productID, serialNumber); +// printf(" UN:%d", CGDisplayUnitNumber(displayID)); +// printf(" IN:%d", iter); + //printf(" Serial:%s\n", CFStringGetCStringPtr(serial, kCFStringEncodingUTF8)); +// printf(" %s %s\n", name, CFStringGetCStringPtr(location, kCFStringEncodingUTF8)); + servicePort = serv; + CFRelease(info); + break; + } + + IOObjectRelease(iter); + return servicePort; +} + IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { kern_return_t kr; io_service_t framebuffer, interface; @@ -18,7 +106,8 @@ IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { IOItemCount busCount; //printf("Querying for displayid: %d\n", display_id); - framebuffer = CGDisplayIOServicePort(display_id); // fixme! - CGDisplayIOServicePort deprecated + //framebuffer = CGDisplayIOServicePort(display_id))) { // Deprecated since OSX 10.9 + framebuffer = IOFramebufferPortFromCGDisplayID(display_id); io_string_t path; kr = IORegistryEntryGetPath(framebuffer, kIOServicePlane, path); From 3bb1d4ad3ac2be34ec691b81f796a912829aa758 Mon Sep 17 00:00:00 2001 From: Saman-VDR Date: Tue, 13 Dec 2016 10:44:23 +0100 Subject: [PATCH 02/15] Fixing and speed up for ddc_read we need a small reply delay to get correct data if the checksum is ok, we don't need a second read replyTransactionType is changed to kIOI2CSimpleTransactionType to get data from my Monitor give it a try ;) --- BrightnessMenulet/ddc.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index b379d25..d3f2936 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -186,13 +186,14 @@ int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand* p_read) { return 0; int successful_reads = 0; + int max_reads = 10; - for (int i=0; i<60; i++) { - bzero( &request, sizeof(request)); + for (int i=0; i 1) - break; - + break; } //fprintf(stderr, "READ ERROR\n"); - } IOI2CInterfaceClose(connect, kNilOptions); + + if (successful_reads == 0) { + printf("Error getting result\n"); + return 0; + } - (*p_read).response.max_value = reply_data[7]; - (*p_read).response.current_value = reply_data[9]; + (*p_read).response.max_value = reply_data[7]; + (*p_read).response.current_value = reply_data[9]; assert(kIOReturnSuccess == kr); if(kIOReturnSuccess != request.result) { From a964f4a80d337f750a2f5e9464be4a0f4f8f40b9 Mon Sep 17 00:00:00 2001 From: Saman-VDR Date: Tue, 13 Dec 2016 10:50:57 +0100 Subject: [PATCH 03/15] skipping screen's, that don't support data reading --- BrightnessMenulet/DDCControls.m | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/BrightnessMenulet/DDCControls.m b/BrightnessMenulet/DDCControls.m index 8048e6f..5387f3e 100644 --- a/BrightnessMenulet/DDCControls.m +++ b/BrightnessMenulet/DDCControls.m @@ -73,6 +73,14 @@ - (void)refreshScreens { // don't want to manage invalid screen or integrated LCD if(!name || [name isEqualToString:@"Color LCD"] || [name isEqualToString:@"iMac"]) continue; + + // skipping screen's, that don't support data reading + struct DDCReadCommand read_command = (struct DDCReadCommand){.control_id = BRIGHTNESS}; + if(ddc_read([screenNumber unsignedIntegerValue] , &read_command) != 1) { + NSLog(@"Reading data from display:%@ failed ", screenNumber); + NSLog(@"... skipping %@ ", name); + continue; + } // Build screen instance NSLog(@"DDCControls: Found %@ - %@", name, screenNumber); From c06e4c2fdcda90c19ae672048d49e1df941e9048 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 13 Dec 2016 11:40:57 +0100 Subject: [PATCH 04/15] Added 'Colors' to preferences --- BrightnessMenulet/Preferences.xib | 185 +++++++++++++++++++--- BrightnessMenulet/PreferencesController.m | 119 +++++++++++++- BrightnessMenulet/Screen.h | 20 +++ BrightnessMenulet/Screen.m | 111 +++++++++++++ 4 files changed, 414 insertions(+), 21 deletions(-) diff --git a/BrightnessMenulet/Preferences.xib b/BrightnessMenulet/Preferences.xib index d725301..07312db 100644 --- a/BrightnessMenulet/Preferences.xib +++ b/BrightnessMenulet/Preferences.xib @@ -1,14 +1,18 @@ - - + + - + + + + + @@ -16,7 +20,13 @@ + + + + + + @@ -27,20 +37,22 @@ - - + + - + - - + + + + @@ -52,6 +64,7 @@ + @@ -62,14 +75,16 @@ - + + - + + @@ -77,6 +92,7 @@ + @@ -84,6 +100,7 @@ + @@ -91,6 +108,7 @@ + @@ -99,6 +117,7 @@ + @@ -107,11 +126,10 @@ - - - + + @@ -128,7 +146,8 @@ - + + - + + @@ -162,6 +184,7 @@ + @@ -173,6 +196,7 @@ + @@ -180,6 +204,7 @@ + @@ -188,11 +213,10 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/BrightnessMenulet/PreferencesController.m b/BrightnessMenulet/PreferencesController.m index 8baec03..0598b9b 100644 --- a/BrightnessMenulet/PreferencesController.m +++ b/BrightnessMenulet/PreferencesController.m @@ -24,9 +24,23 @@ @interface PreferencesController () @property (weak) IBOutlet NSTextField* contCTextField; @property (weak) IBOutlet NSStepper* contCStepper; +// RGB Colors +@property (weak) IBOutlet NSSlider* redCSlider; +@property (weak) IBOutlet NSTextField* redCTextField; +@property (weak) IBOutlet NSStepper* redCStepper; +@property (weak) IBOutlet NSSlider* greenCSlider; +@property (weak) IBOutlet NSTextField* greenCTextField; +@property (weak) IBOutlet NSStepper* greenCStepper; +@property (weak) IBOutlet NSSlider* blueCSlider; +@property (weak) IBOutlet NSTextField* blueCTextField; +@property (weak) IBOutlet NSStepper* blueCStepper; + // If only OSX supported IBOutlet​Collection... @property (strong) NSArray* brightnessOutlets; @property (strong) NSArray* contrastOutlets; +@property (strong) NSArray* redOutlets; +@property (strong) NSArray* greenOutlets; +@property (strong) NSArray* blueOutlets; // Auto-Brightness IBOutlets @property (weak) IBOutlet NSButton *autoBrightOnStartupButton; @@ -54,9 +68,17 @@ - (void)showWindow { [_brightCTextField setFormatter:decFormater]; [_contCTextField setFormatter:decFormater]; + + [_redCTextField setFormatter:decFormater]; + [_greenCTextField setFormatter:decFormater]; + [_blueCTextField setFormatter:decFormater]; _brightnessOutlets = @[_brightCSlider, _brightCTextField, _brightCStepper]; _contrastOutlets = @[_contCSlider, _contCTextField, _contCStepper]; + + _redOutlets = @[_redCSlider, _redCTextField, _redCStepper]; + _greenOutlets = @[_greenCSlider, _greenCTextField, _greenCStepper]; + _blueOutlets = @[_blueCSlider, _blueCTextField, _blueCStepper]; _updateIntervalOutlets = @[_updateIntervalSlider, _updateIntTextField, _updateIntStepper]; @@ -83,6 +105,10 @@ - (void)showWindow { [self updateBrightnessControls]; [self updateContrastControls]; + + [self updateRedControls]; + [self updateGreenControls]; + [self updateBlueControls]; [[self preferenceWindow] makeKeyAndOrderFront:self]; // does not order front? @@ -112,12 +138,50 @@ - (void)updateContrastControls { } } +- (void)updateRedControls { + NSInteger currentRed = _currentScreen.currentRed; + + for(id redOutlet in _redOutlets){ + if(![redOutlet isKindOfClass:[NSTextField class]]) + [redOutlet setMaxValue:_currentScreen.maxRed]; + + [redOutlet setIntValue:currentRed]; + } +} +- (void)updateGreenControls { + NSInteger currentGreen = _currentScreen.currentGreen; + + for(id greenOutlet in _greenOutlets){ + if(![greenOutlet isKindOfClass:[NSTextField class]]) + [greenOutlet setMaxValue:_currentScreen.maxGreen]; + + [greenOutlet setIntValue:currentGreen]; + } +} + +- (void)updateBlueControls { + NSInteger currentBlue = _currentScreen.currentBlue; + + for(id blueOutlet in _blueOutlets){ + if(![blueOutlet isKindOfClass:[NSTextField class]]) + [blueOutlet setMaxValue:_currentScreen.maxBlue]; + + [blueOutlet setIntValue:currentBlue]; + } +} + + + - (void)refreshScreenPopUpList { // Reset Variables [_displayPopUpButton removeAllItems]; [_currentScreen.brightnessOutlets removeObjectsInArray:_brightnessOutlets]; [_currentScreen.contrastOutlets removeObjectsInArray:_contrastOutlets]; + [_currentScreen.redOutlets removeObjectsInArray:_redOutlets]; + [_currentScreen.greenOutlets removeObjectsInArray:_greenOutlets]; + [_currentScreen.blueOutlets removeObjectsInArray:_blueOutlets]; + if([controls.screens count] == 0){ // no screens so disable outlets [_displayPopUpButton setEnabled:NO]; @@ -125,7 +189,9 @@ - (void)refreshScreenPopUpList { // makeObjectsPerformSelector:withObject: only allows NO because it is same as nil lol... [_brightnessOutlets makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO]; [_contrastOutlets makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO]; - + [_redOutlets makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO]; + [_greenOutlets makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO]; + [_blueOutlets makeObjectsPerformSelector:@selector(setEnabled:) withObject:NO]; return; } @@ -145,8 +211,15 @@ - (void)refreshScreenPopUpList { [_currentScreen.brightnessOutlets addObjectsFromArray:_brightnessOutlets]; [_currentScreen.contrastOutlets addObjectsFromArray:_contrastOutlets]; + [_currentScreen.redOutlets addObjectsFromArray:_redOutlets]; + [_currentScreen.greenOutlets addObjectsFromArray:_greenOutlets]; + [_currentScreen.blueOutlets addObjectsFromArray:_blueOutlets]; + [self updateBrightnessControls]; [self updateContrastControls]; + [self updateRedControls]; + [self updateGreenControls]; + [self updateBlueControls]; } #pragma mark - Brightness and Contrast IBActions @@ -157,15 +230,26 @@ - (IBAction)didChangeDisplayMenu:(id)sender { // remove outlets from old screen [_currentScreen.brightnessOutlets removeObjectsInArray:_brightnessOutlets]; [_currentScreen.contrastOutlets removeObjectsInArray:_contrastOutlets]; + + [_currentScreen.redOutlets removeObjectsInArray:_redOutlets]; + [_currentScreen.greenOutlets removeObjectsInArray:_greenOutlets]; + [_currentScreen.blueOutlets removeObjectsInArray:_blueOutlets]; _currentScreen = [controls screenForDisplayName:selectedItem]; // Add outlets to new _currentScreen [_currentScreen.brightnessOutlets addObjectsFromArray:_brightnessOutlets]; [_currentScreen.contrastOutlets addObjectsFromArray:_contrastOutlets]; + + [_currentScreen.redOutlets addObjectsFromArray:_redOutlets]; + [_currentScreen.greenOutlets addObjectsFromArray:_greenOutlets]; + [_currentScreen.blueOutlets addObjectsFromArray:_blueOutlets]; [self updateBrightnessControls]; [self updateContrastControls]; + [self updateRedControls]; + [self updateGreenControls]; + [self updateBlueControls]; } - (IBAction)pressedDebug:(NSButton *)sender { @@ -196,6 +280,36 @@ - (IBAction)contrastOutletValueChanged:(id)sender{ [outlet setIntegerValue:[sender integerValue]]; } +- (IBAction)redOutletValueChanged:(id)sender{ + [_currentScreen setRed:[sender integerValue] byOutlet:sender]; + + NSMutableArray* dirtyOutlets = [_redOutlets mutableCopy]; + [dirtyOutlets removeObject:sender]; + + for(id outlet in dirtyOutlets) + [outlet setIntegerValue:[sender integerValue]]; +} + +- (IBAction)greenOutletValueChanged:(id)sender{ + [_currentScreen setGreen:[sender integerValue] byOutlet:sender]; + + NSMutableArray* dirtyOutlets = [_greenOutlets mutableCopy]; + [dirtyOutlets removeObject:sender]; + + for(id outlet in dirtyOutlets) + [outlet setIntegerValue:[sender integerValue]]; +} + +- (IBAction)blueOutletValueChanged:(id)sender{ + [_currentScreen setBlue:[sender integerValue] byOutlet:sender]; + + NSMutableArray* dirtyOutlets = [_blueOutlets mutableCopy]; + [dirtyOutlets removeObject:sender]; + + for(id outlet in dirtyOutlets) + [outlet setIntegerValue:[sender integerValue]]; +} + #pragma mark - Auto-Brightness IBActions - (IBAction)didToggleAutoBrightOnStartupButton:(NSButton*)sender { @@ -228,6 +342,9 @@ - (IBAction)updateIntOutletValueChanged:(id)sender { - (void)windowWillClose:(NSNotification *)notification { _brightnessOutlets = nil; _contrastOutlets = nil; + _redOutlets = nil; + _greenOutlets = nil; + _blueOutlets = nil; _updateIntervalOutlets = nil; _preferenceWindow = nil; diff --git a/BrightnessMenulet/Screen.h b/BrightnessMenulet/Screen.h index 47f43f3..623999d 100644 --- a/BrightnessMenulet/Screen.h +++ b/BrightnessMenulet/Screen.h @@ -21,9 +21,22 @@ @property (readonly) NSInteger currentContrast; @property (readonly) NSInteger maxContrast; +@property (readonly) NSInteger currentRed; +@property (readonly) NSInteger maxRed; + +@property (readonly) NSInteger currentGreen; +@property (readonly) NSInteger maxGreen; + +@property (readonly) NSInteger currentBlue; +@property (readonly) NSInteger maxBlue; + @property (strong) NSMutableArray* brightnessOutlets; @property (strong) NSMutableArray* contrastOutlets; +@property (strong) NSMutableArray* redOutlets; +@property (strong) NSMutableArray* greenOutlets; +@property (strong) NSMutableArray* blueOutlets; + - (instancetype)initWithModel:(NSString*)model screenID:(CGDirectDisplayID)screenID serial:(NSString*)serial; - (void)refreshValues; @@ -35,4 +48,11 @@ - (void)setContrastWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; - (void)setContrast:(NSInteger)contrast byOutlet:(NSView*)outlet; +- (void)setRedWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; +- (void)setRed:(NSInteger)red byOutlet:(NSView*)outlet; +- (void)setGreenWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; +- (void)setGreen:(NSInteger)green byOutlet:(NSView*)outlet; +- (void)setBlueWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; +- (void)setBlue:(NSInteger)blue byOutlet:(NSView*)outlet; + @end diff --git a/BrightnessMenulet/Screen.m b/BrightnessMenulet/Screen.m index 7e7248c..ace2124 100644 --- a/BrightnessMenulet/Screen.m +++ b/BrightnessMenulet/Screen.m @@ -20,6 +20,13 @@ @interface Screen () @property (readwrite) NSInteger currentContrast; @property (readwrite) NSInteger maxContrast; +@property (readwrite) NSInteger currentRed; +@property (readwrite) NSInteger maxRed; +@property (readwrite) NSInteger currentGreen; +@property (readwrite) NSInteger maxGreen; +@property (readwrite) NSInteger currentBlue; +@property (readwrite) NSInteger maxBlue; + @end @implementation Screen @@ -32,6 +39,9 @@ - (instancetype)initWithModel:(NSString*)model screenID:(CGDirectDisplayID)scree _brightnessOutlets = [NSMutableArray array]; _contrastOutlets = [NSMutableArray array]; + _redOutlets = [NSMutableArray array]; + _greenOutlets = [NSMutableArray array]; + _blueOutlets = [NSMutableArray array]; } return self; @@ -40,12 +50,25 @@ - (instancetype)initWithModel:(NSString*)model screenID:(CGDirectDisplayID)scree - (void)refreshValues { struct DDCReadResponse cBrightness = [controls readDisplay:self.screenNumber controlValue:BRIGHTNESS]; struct DDCReadResponse cContrast = [controls readDisplay:self.screenNumber controlValue:CONTRAST]; + + struct DDCReadResponse cRed = [controls readDisplay:self.screenNumber controlValue:RED_GAIN]; + struct DDCReadResponse cGreen = [controls readDisplay:self.screenNumber controlValue:GREEN_GAIN]; + struct DDCReadResponse cBlue = [controls readDisplay:self.screenNumber controlValue:BLUE_GAIN]; self.currentBrightness = cBrightness.current_value; self.maxBrightness = cBrightness.max_value; self.currentContrast = cContrast.current_value; self.maxContrast = cContrast.max_value; + + self.currentRed = cRed.current_value; + self.maxRed = cRed.max_value; + + self.currentGreen = cGreen.current_value; + self.maxGreen = cGreen.max_value; + + self.currentBlue = cBlue.current_value; + self.maxBlue = cBlue.max_value; NSLog(@"Screen: %@ set BR %ld CON %ld", _model , (long)self.currentBrightness, (long)self.currentContrast); } @@ -114,4 +137,92 @@ - (void)setContrast:(NSInteger)contrast byOutlet:(NSView*)outlet { [dirtyOutlet setIntegerValue:self.currentContrast]; } + + +- (void)setRed:(NSInteger)red { + if(red > self.maxRed) + red = self.maxRed; + + [controls changeDisplay:self.screenNumber control:RED_GAIN withValue: red]; + self.currentRed = red; + + NSLog(@"Screen: %@ - %ud Red changed to %ld", _model, self.screenNumber, (long)self.currentRed); +} + +- (void)setRedWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet { + [self setRed:(self.maxRed * ((double)(percentage)/100)) byOutlet:outlet]; +} + +- (void)setRed:(NSInteger)red byOutlet:(NSView*)outlet { + if(red == self.currentRed) + return; + else + [self setRed:red]; + + NSMutableArray* dirtyOutlets = [_redOutlets mutableCopy]; + if(outlet) + [dirtyOutlets removeObject:outlet]; + + for(id dirtyOutlet in dirtyOutlets) + [dirtyOutlet setIntegerValue:self.currentRed]; +} + + +- (void)setGreen:(NSInteger)green { + if(green > self.maxGreen) + green = self.maxGreen; + + [controls changeDisplay:self.screenNumber control:GREEN_GAIN withValue: green]; + self.currentGreen = green; + + NSLog(@"Screen: %@ - %ud Green changed to %ld", _model, self.screenNumber, (long)self.currentGreen); +} + +- (void)setGreenWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet { + [self setGreen:(self.maxGreen * ((double)(percentage)/100)) byOutlet:outlet]; +} + +- (void)setGreen:(NSInteger)green byOutlet:(NSView*)outlet { + if(green == self.currentGreen) + return; + else + [self setGreen:green]; + + NSMutableArray* dirtyOutlets = [_greenOutlets mutableCopy]; + if(outlet) + [dirtyOutlets removeObject:outlet]; + + for(id dirtyOutlet in dirtyOutlets) + [dirtyOutlet setIntegerValue:self.currentGreen]; +} + + +- (void)setBlue:(NSInteger)blue { + if(blue > self.maxBlue) + blue = self.maxBlue; + + [controls changeDisplay:self.screenNumber control:BLUE_GAIN withValue: blue]; + self.currentBlue = blue; + + NSLog(@"Screen: %@ - %ud Blue changed to %ld", _model, self.screenNumber, (long)self.currentBlue); +} + +- (void)setBlueWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet { + [self setBlue:(self.maxBlue * ((double)(percentage)/100)) byOutlet:outlet]; +} + +- (void)setBlue:(NSInteger)blue byOutlet:(NSView*)outlet { + if(blue == self.currentBlue) + return; + else + [self setBlue:blue]; + + NSMutableArray* dirtyOutlets = [_blueOutlets mutableCopy]; + if(outlet) + [dirtyOutlets removeObject:outlet]; + + for(id dirtyOutlet in dirtyOutlets) + [dirtyOutlet setIntegerValue:self.currentBlue]; +} + @end From 454b4932ad5b52c6cca9631ea503c6006478fd4b Mon Sep 17 00:00:00 2001 From: Marc Date: Wed, 14 Dec 2016 23:57:42 +0100 Subject: [PATCH 05/15] Global keyboard bindings --- BrightnessMenulet/AppDelegate.m | 9 + .../project.pbxproj | 14 +- BrightnessMenulet/MainMenuController.h | 3 + BrightnessMenulet/MainMenuController.m | 101 ++++++++++- BrightnessMenulet/Screen.h | 2 + BrightnessMenulet/Screen.m | 37 ++++ BrightnessMenulet/VirtualKeyCodes.h | 160 ++++++++++++++++++ 7 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 BrightnessMenulet/VirtualKeyCodes.h diff --git a/BrightnessMenulet/AppDelegate.m b/BrightnessMenulet/AppDelegate.m index 2762588..166a074 100644 --- a/BrightnessMenulet/AppDelegate.m +++ b/BrightnessMenulet/AppDelegate.m @@ -16,6 +16,7 @@ @interface AppDelegate () @end + @implementation AppDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { @@ -46,6 +47,9 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { if([defaults boolForKey:@"autoBrightOnStartup"]) [lmuCon startMonitoring]; + + // Unregister hotkeys + [_mainMenu registerHotKeys]; } - (void)applicationDidChangeScreenParameters:(NSNotification *)notification { @@ -56,4 +60,9 @@ - (void)applicationDidChangeScreenParameters:(NSNotification *)notification { [_mainMenu refreshMenuScreens]; } +- (void)applicationWillTerminate:(NSNotification *)aNotification { + // Unregister hotkeys + [_mainMenu unregisterHotKeys]; +} + @end diff --git a/BrightnessMenulet/BrightnessMenulet.xcodeproj/project.pbxproj b/BrightnessMenulet/BrightnessMenulet.xcodeproj/project.pbxproj index 32c91ba..b1fae05 100755 --- a/BrightnessMenulet/BrightnessMenulet.xcodeproj/project.pbxproj +++ b/BrightnessMenulet/BrightnessMenulet.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 0B4E1FEC19E5FD4B0044BAFE /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0B4E1FEB19E5FD4B0044BAFE /* Preferences.xib */; }; 0B4E1FEF19E5FEBB0044BAFE /* PreferencesController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B4E1FEE19E5FEBB0044BAFE /* PreferencesController.m */; }; 0BCFE67319C9F1B40094B561 /* DDCControls.m in Sources */ = {isa = PBXBuildFile; fileRef = 0BCFE67219C9F1B30094B561 /* DDCControls.m */; }; + 498A53C91E00AA190036FC5A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 498A53C81E00AA190036FC5A /* Carbon.framework */; }; 7D2FF98311406B0100707C79 /* MainMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 7D2FF98211406B0100707C79 /* MainMenuController.m */; }; 7D2FF98611406B2C00707C79 /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D2FF98411406B2C00707C79 /* icon.png */; }; 7D2FF98711406B2C00707C79 /* icon-alt.png in Resources */ = {isa = PBXBuildFile; fileRef = 7D2FF98511406B2C00707C79 /* icon-alt.png */; }; @@ -37,6 +38,8 @@ 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; 32CA4F630368D1EE00C91783 /* BrightnessMenulet_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BrightnessMenulet_Prefix.pch; sourceTree = ""; }; + 498493181E01EFFA00FA89FD /* VirtualKeyCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VirtualKeyCodes.h; sourceTree = ""; }; + 498A53C81E00AA190036FC5A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; 7D2FF98111406B0100707C79 /* MainMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainMenuController.h; sourceTree = ""; }; 7D2FF98211406B0100707C79 /* MainMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainMenuController.m; sourceTree = ""; }; 7D2FF98411406B2C00707C79 /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = ""; }; @@ -60,6 +63,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 498A53C91E00AA190036FC5A /* Carbon.framework in Frameworks */, 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, 7D2FF9FC1140B94600707C79 /* IOKit.framework in Frameworks */, ); @@ -138,6 +142,7 @@ 29B97314FDCFA39411CA2CEA /* MyStatusItem */ = { isa = PBXGroup; children = ( + 498493181E01EFFA00FA89FD /* VirtualKeyCodes.h */, 0B38B5E419E8EF9200D72911 /* AppDelegate.h */, 0B38B5E519E8EF9200D72911 /* AppDelegate.m */, 0B8CF6CC19E71BA400F3FD6F /* Menu */, @@ -165,6 +170,7 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 498A53C81E00AA190036FC5A /* Carbon.framework */, 79897D331664F808008C7D5C /* ddc */, 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, @@ -222,7 +228,7 @@ LastUpgradeCheck = 0700; TargetAttributes = { 8D1107260486CEB800E47090 = { - DevelopmentTeam = N6A2S4TCDQ; + ProvisioningStyle = Manual; }; }; }; @@ -281,9 +287,10 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEVELOPMENT_TEAM = ""; GCC_DYNAMIC_NO_PIC = NO; GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_MODEL_TUNING = G5; @@ -306,9 +313,10 @@ buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ENABLE_OBJC_ARC = YES; - "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Mac Developer"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = ""; GCC_MODEL_TUNING = G5; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = BrightnessMenulet_Prefix.pch; diff --git a/BrightnessMenulet/MainMenuController.h b/BrightnessMenulet/MainMenuController.h index 69ad10f..bdc1374 100755 --- a/BrightnessMenulet/MainMenuController.h +++ b/BrightnessMenulet/MainMenuController.h @@ -14,4 +14,7 @@ - (void)refreshMenuScreens; +- (void)registerHotKeys; +- (void)unregisterHotKeys; + @end diff --git a/BrightnessMenulet/MainMenuController.m b/BrightnessMenulet/MainMenuController.m index b4f71b3..67ab9ee 100755 --- a/BrightnessMenulet/MainMenuController.m +++ b/BrightnessMenulet/MainMenuController.m @@ -11,14 +11,21 @@ #import "MainMenuController.h" #import "PreferencesController.h" -@interface MainMenuController () +#import "VirtualKeyCodes.h" +#import + +@interface MainMenuController () { + EventHotKeyRef hotKeyRef; +} @property PreferencesController* preferencesController; @property (weak) IBOutlet NSMenuItem *autoBrightnessItem; +@property (assign, nonatomic) BOOL darkModeOn; @end + @implementation MainMenuController - (void)refreshMenuScreens { @@ -98,7 +105,8 @@ - (void)sliderUpdate:(NSSlider*)slider { } - (IBAction)quit:(id)sender { - exit(1); + //exit(1); + [[NSApplication sharedApplication] terminate:self]; } #pragma mark - LMUDelegate @@ -111,4 +119,93 @@ - (void)LMUControllerDidStopMonitoring { [_autoBrightnessItem setState:NSOffState]; } +#pragma mark - Global HotKeys +-(void)registerHotKeys +{ + //EventHotKeyRef hotKeyRef; + if (!hotKeyRef) { + NSLog(@"Register HotKeys"); + + EventHotKeyID hotKeyID; + EventTypeSpec eventType; + eventType.eventClass=kEventClassKeyboard; + eventType.eventKind=kEventHotKeyPressed; + + InstallApplicationEventHandler(&OnHotKeyEvent, 1, &eventType, (void *)CFBridgingRetain(self), NULL); + + hotKeyID.signature='htk1'; + hotKeyID.id=1; + RegisterEventHotKey(kVK_ANSI_T, cmdKey+optionKey, hotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef); + + hotKeyID.signature='htk2'; + hotKeyID.id=2; + RegisterEventHotKey(kVK_BrightnessUp, 0, hotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef); + + hotKeyID.signature='htk3'; + hotKeyID.id=3; + RegisterEventHotKey(kVK_BrightnessDown, 0, hotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef); + + hotKeyID.signature='htk4'; + hotKeyID.id=4; + RegisterEventHotKey(kVK_BrightnessUp, cmdKey+optionKey, hotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef); + + hotKeyID.signature='htk5'; + hotKeyID.id=5; + RegisterEventHotKey(kVK_BrightnessDown, cmdKey+optionKey, hotKeyID, GetApplicationEventTarget(), 0, &hotKeyRef); + } +} + +- (void)unregisterHotKeys { + if (hotKeyRef) { + NSLog(@"Unregister HotKeys"); + UnregisterEventHotKey(hotKeyRef); + hotKeyRef = 0; + } +} + +OSStatus OnHotKeyEvent(EventHandlerCallRef nextHandler, EventRef theEvent, void *userData) +{ + EventHotKeyID hkCom; + + GetEventParameter(theEvent, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hkCom), NULL, &hkCom); + MainMenuController *app = (__bridge MainMenuController *)userData; + + int l = hkCom.id; + + switch (l) { + case 1: + NSLog(@"Capture COMMAND + OPTION + T"); + [app helloWorld]; + break; + case 2: + NSLog(@"Capture BRIGHTNESS_UP"); + if ([controls.screens count] < 1) return 0; + [controls.screens[0] setBrightnessRelativeToValue:@"5+"]; + break; + case 3: + NSLog(@"Capture BRIGHTNESS_DOWN"); + if ([controls.screens count] < 1) return 0; + [controls.screens[0] setBrightnessRelativeToValue:@"5-"]; + break; + case 4: + NSLog(@"Capture COMMAND + OPTION + BRIGHTNESS_UP"); + for(Screen* screen in controls.screens) { + [[controls screenForDisplayID:screen.screenNumber] setBrightnessRelativeToValue:@"5+"]; + } + break; + case 5: + NSLog(@"Capture COMMAND + OPTION + BRIGHTNESS_DOWN"); + for(Screen* screen in controls.screens) { + [[controls screenForDisplayID:screen.screenNumber] setBrightnessRelativeToValue:@"5-"]; + } + break; + } + + return noErr; +} + +- (void)helloWorld { + NSLog(@"Hello World"); +} + @end diff --git a/BrightnessMenulet/Screen.h b/BrightnessMenulet/Screen.h index 623999d..d6567e9 100644 --- a/BrightnessMenulet/Screen.h +++ b/BrightnessMenulet/Screen.h @@ -44,9 +44,11 @@ - (void)setBrightnessWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; - (void)setBrightness:(NSInteger)brightness byOutlet:(NSView*)outlet; +- (void)setBrightnessRelativeToValue:(NSString *)value; - (void)setContrastWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; - (void)setContrast:(NSInteger)contrast byOutlet:(NSView*)outlet; +- (void)setContrastRelativeToValue:(NSString *)value; - (void)setRedWithPercentage:(NSInteger)percentage byOutlet:(NSView*)outlet; - (void)setRed:(NSInteger)red byOutlet:(NSView*)outlet; diff --git a/BrightnessMenulet/Screen.m b/BrightnessMenulet/Screen.m index ace2124..277c4e0 100644 --- a/BrightnessMenulet/Screen.m +++ b/BrightnessMenulet/Screen.m @@ -109,6 +109,24 @@ - (void)setBrightness:(NSInteger)brightness byOutlet:(NSView*)outlet { [dirtyOutlet setIntegerValue:self.currentBrightness]; } +- (void)setBrightnessRelativeToValue:(NSString *)value { + // relative setting: read, calculate, then write + NSString *value_num = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"-+"]]; + NSString *formula = [NSString stringWithFormat:@"%ld %@ %@", (long)self.currentBrightness, [value substringFromIndex:value.length - 1], value_num]; + NSExpression *exp = [NSExpression expressionWithFormat:formula]; + NSNumber *value_set = [exp expressionValueWithObject:nil context:nil]; + + if ((value_set.intValue > self.maxContrast) || (value_set.intValue < 0)) return; + + [controls changeDisplay:self.screenNumber control:BRIGHTNESS withValue: value_set.intValue]; + self.currentBrightness = value_set.intValue; + + // update the sliders, this fails with multiple screens + for(id outlet in _brightnessOutlets) [outlet setIntegerValue:self.currentBrightness]; + + NSLog(@"Screen: %@ - %ud Brightness changed to %ld", _model, self.screenNumber, (long)self.currentBrightness); +} + - (void)setContrast:(NSInteger)contrast { if(contrast > self.maxContrast) contrast = self.maxContrast; @@ -137,6 +155,25 @@ - (void)setContrast:(NSInteger)contrast byOutlet:(NSView*)outlet { [dirtyOutlet setIntegerValue:self.currentContrast]; } +- (void)setContrastRelativeToValue:(NSString *)value { + // relative setting: read, calculate, then write + NSString *value_num = [value stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"-+"]]; + NSString *formula = [NSString stringWithFormat:@"%ld %@ %@", (long)self.currentContrast, [value substringFromIndex:value.length - 1], value_num]; + NSExpression *exp = [NSExpression expressionWithFormat:formula]; + NSNumber *value_set = [exp expressionValueWithObject:nil context:nil]; + + if ((value_set.intValue > self.maxContrast) || (value_set.intValue < 0)) return; + + [controls changeDisplay:self.screenNumber control:CONTRAST withValue: value_set.intValue]; + self.currentContrast = value_set.intValue; + + + // update the sliders, this fails with multiple screens + for(id outlet in _contrastOutlets) [outlet setIntegerValue:self.currentContrast]; + + NSLog(@"Screen: %@ - %ud Contrast changed to %ld", _model, self.screenNumber, (long)self.currentContrast); +} + - (void)setRed:(NSInteger)red { diff --git a/BrightnessMenulet/VirtualKeyCodes.h b/BrightnessMenulet/VirtualKeyCodes.h new file mode 100644 index 0000000..e8142cb --- /dev/null +++ b/BrightnessMenulet/VirtualKeyCodes.h @@ -0,0 +1,160 @@ +// +// VirtualKeyCodes.h +// BrightnessMenulet +// +// Created by Marc on 14.12.16. +// +// + +#ifndef VirtualKeyCodes_h +#define VirtualKeyCodes_h + +// carbon modifier flags +// cmdKey optionKey controlKey shiftKey; + + +/* additional keycodes */ +enum { + kVK_BrightnessUp = 0x90, + kVK_BrightnessDown = 0x91 +}; + +// >>> Source: HIToolbox/Events.h + +/* + * Summary: + * Virtual keycodes + * + * Discussion: + * These constants are the virtual keycodes defined originally in + * Inside Mac Volume V, pg. V-191. They identify physical keys on a + * keyboard. Those constants with "ANSI" in the name are labeled + * according to the key position on an ANSI-standard US keyboard. + * For example, kVK_ANSI_A indicates the virtual keycode for the key + * with the letter 'A' in the US keyboard layout. Other keyboard + * layouts may have the 'A' key label on a different physical key; + * in this case, pressing 'A' will generate a different virtual + * keycode. + * / +enum { + kVK_ANSI_A = 0x00, + kVK_ANSI_S = 0x01, + kVK_ANSI_D = 0x02, + kVK_ANSI_F = 0x03, + kVK_ANSI_H = 0x04, + kVK_ANSI_G = 0x05, + kVK_ANSI_Z = 0x06, + kVK_ANSI_X = 0x07, + kVK_ANSI_C = 0x08, + kVK_ANSI_V = 0x09, + kVK_ANSI_B = 0x0B, + kVK_ANSI_Q = 0x0C, + kVK_ANSI_W = 0x0D, + kVK_ANSI_E = 0x0E, + kVK_ANSI_R = 0x0F, + kVK_ANSI_Y = 0x10, + kVK_ANSI_T = 0x11, + kVK_ANSI_1 = 0x12, + kVK_ANSI_2 = 0x13, + kVK_ANSI_3 = 0x14, + kVK_ANSI_4 = 0x15, + kVK_ANSI_6 = 0x16, + kVK_ANSI_5 = 0x17, + kVK_ANSI_Equal = 0x18, + kVK_ANSI_9 = 0x19, + kVK_ANSI_7 = 0x1A, + kVK_ANSI_Minus = 0x1B, + kVK_ANSI_8 = 0x1C, + kVK_ANSI_0 = 0x1D, + kVK_ANSI_RightBracket = 0x1E, + kVK_ANSI_O = 0x1F, + kVK_ANSI_U = 0x20, + kVK_ANSI_LeftBracket = 0x21, + kVK_ANSI_I = 0x22, + kVK_ANSI_P = 0x23, + kVK_ANSI_L = 0x25, + kVK_ANSI_J = 0x26, + kVK_ANSI_Quote = 0x27, + kVK_ANSI_K = 0x28, + kVK_ANSI_Semicolon = 0x29, + kVK_ANSI_Backslash = 0x2A, + kVK_ANSI_Comma = 0x2B, + kVK_ANSI_Slash = 0x2C, + kVK_ANSI_N = 0x2D, + kVK_ANSI_M = 0x2E, + kVK_ANSI_Period = 0x2F, + kVK_ANSI_Grave = 0x32, + kVK_ANSI_KeypadDecimal = 0x41, + kVK_ANSI_KeypadMultiply = 0x43, + kVK_ANSI_KeypadPlus = 0x45, + kVK_ANSI_KeypadClear = 0x47, + kVK_ANSI_KeypadDivide = 0x4B, + kVK_ANSI_KeypadEnter = 0x4C, + kVK_ANSI_KeypadMinus = 0x4E, + kVK_ANSI_KeypadEquals = 0x51, + kVK_ANSI_Keypad0 = 0x52, + kVK_ANSI_Keypad1 = 0x53, + kVK_ANSI_Keypad2 = 0x54, + kVK_ANSI_Keypad3 = 0x55, + kVK_ANSI_Keypad4 = 0x56, + kVK_ANSI_Keypad5 = 0x57, + kVK_ANSI_Keypad6 = 0x58, + kVK_ANSI_Keypad7 = 0x59, + kVK_ANSI_Keypad8 = 0x5B, + kVK_ANSI_Keypad9 = 0x5C +}; +*/ +/* keycodes for keys that are independent of keyboard layout * / +enum { + kVK_Return = 0x24, + kVK_Tab = 0x30, + kVK_Space = 0x31, + kVK_Delete = 0x33, + kVK_Escape = 0x35, + kVK_Command = 0x37, + kVK_Shift = 0x38, + kVK_CapsLock = 0x39, + kVK_Option = 0x3A, + kVK_Control = 0x3B, + kVK_RightCommand = 0x36, + kVK_RightShift = 0x3C, + kVK_RightOption = 0x3D, + kVK_RightControl = 0x3E, + kVK_Function = 0x3F, + kVK_F17 = 0x40, + kVK_VolumeUp = 0x48, + kVK_VolumeDown = 0x49, + kVK_Mute = 0x4A, + kVK_F18 = 0x4F, + kVK_F19 = 0x50, + kVK_F20 = 0x5A, + kVK_F5 = 0x60, + kVK_F6 = 0x61, + kVK_F7 = 0x62, + kVK_F3 = 0x63, + kVK_F8 = 0x64, + kVK_F9 = 0x65, + kVK_F11 = 0x67, + kVK_F13 = 0x69, + kVK_F16 = 0x6A, + kVK_F14 = 0x6B, + kVK_F10 = 0x6D, + kVK_F12 = 0x6F, + kVK_F15 = 0x71, + kVK_Help = 0x72, + kVK_Home = 0x73, + kVK_PageUp = 0x74, + kVK_ForwardDelete = 0x75, + kVK_F4 = 0x76, + kVK_End = 0x77, + kVK_F2 = 0x78, + kVK_PageDown = 0x79, + kVK_F1 = 0x7A, + kVK_LeftArrow = 0x7B, + kVK_RightArrow = 0x7C, + kVK_DownArrow = 0x7D, + kVK_UpArrow = 0x7E +}; +*/ + +#endif /* VirtualKeyCodes_h */ From c6c0b59dcca8654600e6afa8f6837755f9c056ac Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 15 Dec 2016 14:16:12 +0100 Subject: [PATCH 06/15] Don't run twice and don't block the main thread --- BrightnessMenulet/AppDelegate.m | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/BrightnessMenulet/AppDelegate.m b/BrightnessMenulet/AppDelegate.m index 166a074..f1e8a6c 100644 --- a/BrightnessMenulet/AppDelegate.m +++ b/BrightnessMenulet/AppDelegate.m @@ -19,6 +19,19 @@ @interface AppDelegate () @implementation AppDelegate +- (void)awakeFromNib +{ + NSLog(@"%@ %@", + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"], + [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]); + + if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]] count] > 1) { + NSLog(@"... is already running!"); + [NSApp terminate:nil]; + } +} + + - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Set Menulet Icon NSBundle *bundle = [NSBundle mainBundle]; @@ -37,8 +50,10 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // init _mainMenu [_mainMenu refreshMenuScreens]; + // LMU [LMUController singleton]; lmuCon.delegate = _mainMenu; + NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; @@ -56,8 +71,11 @@ - (void)applicationDidChangeScreenParameters:(NSNotification *)notification { NSLog(@"AppDelegate: DidChangeScreenParameters"); // BUG: May crash if displays are connected/disconnected quickly so lets try waiting - [NSThread sleepForTimeInterval:2.0f]; - [_mainMenu refreshMenuScreens]; + [NSTimer scheduledTimerWithTimeInterval:2.0f + target:_mainMenu + selector:@selector(refreshMenuScreens) + userInfo:nil + repeats:NO]; } - (void)applicationWillTerminate:(NSNotification *)aNotification { From b3ce03a0501a1bd68bb67a9da3da0e26e6fa57a4 Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 15 Dec 2016 14:22:39 +0100 Subject: [PATCH 07/15] Add missing CFBundleDisplayName --- BrightnessMenulet/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/BrightnessMenulet/Info.plist b/BrightnessMenulet/Info.plist index 4b1c0ef..2a01afa 100755 --- a/BrightnessMenulet/Info.plist +++ b/BrightnessMenulet/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion English + CFBundleDisplayName + ${PRODUCT_NAME} CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile From 06d40d386f8bdc65920c4a8ef11cf0d104689f16 Mon Sep 17 00:00:00 2001 From: Marc Date: Thu, 15 Dec 2016 22:09:27 +0100 Subject: [PATCH 08/15] Remove menu item if LMU is not available --- BrightnessMenulet/LMUController.h | 1 + BrightnessMenulet/LMUController.m | 3 +++ BrightnessMenulet/MainMenuController.m | 8 ++++++++ 3 files changed, 12 insertions(+) diff --git a/BrightnessMenulet/LMUController.h b/BrightnessMenulet/LMUController.h index 9854aef..a68a3fe 100644 --- a/BrightnessMenulet/LMUController.h +++ b/BrightnessMenulet/LMUController.h @@ -16,6 +16,7 @@ @property (weak) id delegate; +@property BOOL available; @property BOOL monitoring; + (LMUController*)singleton; diff --git a/BrightnessMenulet/LMUController.m b/BrightnessMenulet/LMUController.m index 72e11f8..39d1470 100644 --- a/BrightnessMenulet/LMUController.m +++ b/BrightnessMenulet/LMUController.m @@ -50,11 +50,13 @@ - (io_connect_t)getLMUDataPort { io_service_t serviceObject; if(_lmuDataPort) return _lmuDataPort; + self.available = YES; serviceObject = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("AppleLMUController")); if(!serviceObject){ NSLog(@"LMUController: Failed to find LMU\n"); + self.available = NO; return 0; } @@ -64,6 +66,7 @@ - (io_connect_t)getLMUDataPort { if(kr != KERN_SUCCESS){ NSLog(@"LMUController: Failed to open LMU IOService object"); + self.available = NO; return 0; } diff --git a/BrightnessMenulet/MainMenuController.m b/BrightnessMenulet/MainMenuController.m index 67ab9ee..6403ce0 100755 --- a/BrightnessMenulet/MainMenuController.m +++ b/BrightnessMenulet/MainMenuController.m @@ -45,6 +45,14 @@ - (void)refreshMenuScreens { [lmuCon stopMonitoring]; return; } + + // No LMU available + if(!lmuCon.available) { + if(self.autoBrightnessItem) { + [self removeItem:self.autoBrightnessItem]; + NSLog(@"Remove 'Auto-Brightness' menu item"); + } + } // add new outlets for screens for(Screen* screen in controls.screens){ From 18f289362b49b61e295f7cc8ace7e2ebca359196 Mon Sep 17 00:00:00 2001 From: Marc Date: Sun, 18 Dec 2016 15:31:35 +0100 Subject: [PATCH 09/15] Release the framebuffer object --- BrightnessMenulet/ddc.c | 87 ++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index d3f2936..75d22da 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -11,23 +11,23 @@ #include #include "ddc.h" -// IOFramebufferPortFromCGDisplayID from https://github.com/kfix/ddcctl/blob/master/DDC.h +/* + IOFramebufferPortFromCGDisplayID based on: https://github.com/kfix/ddcctl/commit/0d66010890f99aa0972bb1478b41dda6329f52b4 + + Iterate IOreg's device tree to find the IOFramebuffer mach service port that corresponds to a given CGDisplayID + replaces CGDisplayIOServicePort: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/Quartz_Services_Ref/index.html#//apple_ref/c/func/CGDisplayIOServicePort + based on: https://github.com/glfw/glfw/pull/192/files + */ #include - static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID) -// iterate IOreg's device tree to find the IOFramebuffer mach service port that corresponds to a given CGDisplayID -// replaces CGDisplayIOServicePort: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/Quartz_Services_Ref/index.html#//apple_ref/c/func/CGDisplayIOServicePort -// based on: https://github.com/glfw/glfw/pull/192/files { io_iterator_t iter; io_service_t serv, servicePort = 0; - kern_return_t err = IOServiceGetMatchingServices( kIOMasterPortDefault, - IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), // IOFramebufferI2CInterface - &iter); + kern_return_t err = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), &iter); if (err != KERN_SUCCESS) - return 0; + return 0; // now recurse the IOReg tree while ((serv = IOIteratorNext(iter)) != MACH_PORT_NULL) @@ -36,26 +36,29 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID io_name_t name; CFIndex vendorID, productID, serialNumber = 0; CFNumberRef vendorIDRef, productIDRef, serialNumberRef; +#ifdef DEBUG CFStringRef location = CFSTR(""); - //CFStringRef serial = CFSTR(""); + CFStringRef serial = CFSTR(""); +#endif Boolean success = 0; // get metadata from IOreg node IORegistryEntryGetName(serv, name); info = IODisplayCreateInfoDictionary(serv, kIODisplayOnlyPreferredName); +#ifdef DEBUG /* When assigning a display ID, Quartz considers the following parameters:Vendor, Model, Serial Number and Position in the I/O Kit registry */ // http://opensource.apple.com//source/IOGraphics/IOGraphics-179.2/IOGraphicsFamily/IOKit/graphics/IOGraphicsTypes.h CFStringRef locationRef = CFDictionaryGetValue(info, CFSTR(kIODisplayLocationKey)); - location = CFStringCreateCopy(NULL, locationRef); - //CFStringRef serialRef = CFDictionaryGetValue(info, CFSTR(kDisplaySerialString)); - //serial = CFStringCreateCopy(NULL, serialRef); - + if (locationRef) location = CFStringCreateCopy(NULL, locationRef); + CFStringRef serialRef = CFDictionaryGetValue(info, CFSTR(kDisplaySerialString)); + if (serialRef) serial = CFStringCreateCopy(NULL, serialRef); +#endif if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayVendorID), (const void**)&vendorIDRef)) - success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); + success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayProductID), (const void**)&productIDRef)) - success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); + success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); IOItemCount busCount; IOFBGetI2CInterfaceCount(serv, &busCount); @@ -73,23 +76,25 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID } if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplaySerialNumber), (const void**)&serialNumberRef)) - CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); + CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); // compare IOreg's metadata to CGDisplay's metadata to infer if the IOReg's I2C monitor is the display for the given NSScreen.displayID - if (CGDisplayVendorNumber(displayID) != vendorID || - CGDisplayModelNumber(displayID) != productID || - CGDisplaySerialNumber(displayID) != serialNumber ) // SN is zero in lots of cases, so duplicate-monitors can confuse us :-/ + if (CGDisplayVendorNumber(displayID) != vendorID || + CGDisplayModelNumber(displayID) != productID || + CGDisplaySerialNumber(displayID) != serialNumber) // SN is zero in lots of cases, so duplicate-monitors can confuse us :-/ { CFRelease(info); continue; } - +#ifdef DEBUG // considering this IOFramebuffer as the match for the CGDisplay, dump out its information -// printf("VN:%ld PN:%ld SN:%ld", vendorID, productID, serialNumber); -// printf(" UN:%d", CGDisplayUnitNumber(displayID)); -// printf(" IN:%d", iter); - //printf(" Serial:%s\n", CFStringGetCStringPtr(serial, kCFStringEncodingUTF8)); -// printf(" %s %s\n", name, CFStringGetCStringPtr(location, kCFStringEncodingUTF8)); + printf("\nFramebuffer: %s\n", name); + printf("%s\n", CFStringGetCStringPtr(location, kCFStringEncodingUTF8)); + printf("VN:%ld PN:%ld SN:%ld", vendorID, productID, serialNumber); + printf(" UN:%d", CGDisplayUnitNumber(displayID)); + printf(" IN:%d", iter); + printf(" Serial:%s\n\n", CFStringGetCStringPtr(serial, kCFStringEncodingUTF8)); +#endif servicePort = serv; CFRelease(info); break; @@ -104,36 +109,38 @@ IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { io_service_t framebuffer, interface; IOOptionBits bus; IOItemCount busCount; - + //printf("Querying for displayid: %d\n", display_id); - //framebuffer = CGDisplayIOServicePort(display_id))) { // Deprecated since OSX 10.9 + //framebuffer = CGDisplayIOServicePort(display_id))) // Deprecated since OSX 10.9 framebuffer = IOFramebufferPortFromCGDisplayID(display_id); - + io_string_t path; kr = IORegistryEntryGetPath(framebuffer, kIOServicePlane, path); if(KERN_SUCCESS != kr) // display path find failed - return nil; - - kr = IOFBGetI2CInterfaceCount( framebuffer, &busCount ); + return nil; + + kr = IOFBGetI2CInterfaceCount(framebuffer, &busCount ); assert(kIOReturnSuccess == kr); - + for(bus = 0; bus < busCount; bus++){ IOI2CConnectRef connect; - + kr = IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface); if(kIOReturnSuccess != kr) - continue; - + continue; + kr = IOI2CInterfaceOpen(interface, kNilOptions, &connect); - + IOObjectRelease(interface); assert(kIOReturnSuccess == kr); if(kIOReturnSuccess != kr) - continue; - + continue; + + IOObjectRelease(framebuffer); return connect; } - + + IOObjectRelease(framebuffer); return nil; } From 6dee4785b22694673ff4a91e2c293bed3fa2178a Mon Sep 17 00:00:00 2001 From: Marc Date: Sun, 18 Dec 2016 15:57:51 +0100 Subject: [PATCH 10/15] Fixing tabs --- BrightnessMenulet/ddc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index 75d22da..df49009 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -27,7 +27,7 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID kern_return_t err = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), &iter); if (err != KERN_SUCCESS) - return 0; + return 0; // now recurse the IOReg tree while ((serv = IOIteratorNext(iter)) != MACH_PORT_NULL) @@ -117,7 +117,7 @@ IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { io_string_t path; kr = IORegistryEntryGetPath(framebuffer, kIOServicePlane, path); if(KERN_SUCCESS != kr) // display path find failed - return nil; + return nil; kr = IOFBGetI2CInterfaceCount(framebuffer, &busCount ); assert(kIOReturnSuccess == kr); @@ -127,14 +127,14 @@ IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { kr = IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface); if(kIOReturnSuccess != kr) - continue; + continue; kr = IOI2CInterfaceOpen(interface, kNilOptions, &connect); IOObjectRelease(interface); assert(kIOReturnSuccess == kr); if(kIOReturnSuccess != kr) - continue; + continue; IOObjectRelease(framebuffer); return connect; From 37fbf4a054668d55dcd61b51035cd87de93ae8ee Mon Sep 17 00:00:00 2001 From: Marc Date: Sun, 18 Dec 2016 20:22:37 +0100 Subject: [PATCH 11/15] Change README --- BrightnessMenulet/Brightness_Menulet.zip | Bin 81552 -> 0 bytes README.rst | 45 +++-------------------- 2 files changed, 6 insertions(+), 39 deletions(-) delete mode 100644 BrightnessMenulet/Brightness_Menulet.zip diff --git a/BrightnessMenulet/Brightness_Menulet.zip b/BrightnessMenulet/Brightness_Menulet.zip deleted file mode 100644 index ec25e84ddc92e4f92e7f9fbf68ff9810227bd7ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 81552 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 Date: Tue, 10 Jan 2017 07:45:47 -0800 Subject: [PATCH 12/15] Update to latest DDC --- BrightnessMenulet/DDCControls.h | 2 +- BrightnessMenulet/DDCControls.m | 12 +- BrightnessMenulet/Screen.m | 12 +- BrightnessMenulet/ddc.c | 428 +++++++++++++++++++------------- BrightnessMenulet/ddc.h | 96 +++---- 5 files changed, 310 insertions(+), 240 deletions(-) diff --git a/BrightnessMenulet/DDCControls.h b/BrightnessMenulet/DDCControls.h index 6b67b6b..830bedb 100644 --- a/BrightnessMenulet/DDCControls.h +++ b/BrightnessMenulet/DDCControls.h @@ -19,7 +19,7 @@ + (DDCControls*)singleton; - (NSString*)EDIDString:(char*) string; -- (struct DDCReadResponse)readDisplay:(CGDirectDisplayID)display_id controlValue:(int)control; +- (struct DDCReadCommand)readDisplay:(CGDirectDisplayID)display_id controlValue:(int)control; - (void)changeDisplay:(CGDirectDisplayID)display_id control:(int)control withValue:(int)value; - (void)refreshScreens; diff --git a/BrightnessMenulet/DDCControls.m b/BrightnessMenulet/DDCControls.m index 5387f3e..7ff8310 100644 --- a/BrightnessMenulet/DDCControls.m +++ b/BrightnessMenulet/DDCControls.m @@ -29,19 +29,19 @@ - (NSString*)EDIDString:(char*) string { : temp; } -- (struct DDCReadResponse)readDisplay:(CGDirectDisplayID)display_id controlValue:(int)control{ +- (struct DDCReadCommand)readDisplay:(CGDirectDisplayID)display_id controlValue:(int)control{ struct DDCReadCommand read_command = (struct DDCReadCommand){.control_id = control}; - if(ddc_read(display_id, &read_command) != 1) + if(DDCRead(display_id, &read_command) != 1) NSLog(@"readDisplay:%u controlValue: failed need to retry...", display_id); - return read_command.response; + return read_command; } - (void)changeDisplay:(CGDirectDisplayID)display_id control:(int)control withValue:(int)value{ struct DDCWriteCommand write_command = (struct DDCWriteCommand){.control_id = control, .new_value = value}; - if(ddc_write(display_id, &write_command) != 1) + if(DDCWrite(display_id, &write_command) != 1) NSLog(@"writeDisplay:%u withValue: failed need to retry...", display_id); } @@ -55,7 +55,7 @@ - (void)refreshScreens { // Fetch Monitor info via EDID struct EDID edid = {}; - EDIDRead([screenNumber unsignedIntegerValue], &edid); + EDIDTest([screenNumber unsignedIntegerValue], &edid); NSString* name; NSString* serial; @@ -76,7 +76,7 @@ - (void)refreshScreens { // skipping screen's, that don't support data reading struct DDCReadCommand read_command = (struct DDCReadCommand){.control_id = BRIGHTNESS}; - if(ddc_read([screenNumber unsignedIntegerValue] , &read_command) != 1) { + if(DDCRead([screenNumber unsignedIntegerValue] , &read_command) != 1) { NSLog(@"Reading data from display:%@ failed ", screenNumber); NSLog(@"... skipping %@ ", name); continue; diff --git a/BrightnessMenulet/Screen.m b/BrightnessMenulet/Screen.m index 277c4e0..7fbb187 100644 --- a/BrightnessMenulet/Screen.m +++ b/BrightnessMenulet/Screen.m @@ -48,12 +48,12 @@ - (instancetype)initWithModel:(NSString*)model screenID:(CGDirectDisplayID)scree } - (void)refreshValues { - struct DDCReadResponse cBrightness = [controls readDisplay:self.screenNumber controlValue:BRIGHTNESS]; - struct DDCReadResponse cContrast = [controls readDisplay:self.screenNumber controlValue:CONTRAST]; + struct DDCReadCommand cBrightness = [controls readDisplay:self.screenNumber controlValue:BRIGHTNESS]; + struct DDCReadCommand cContrast = [controls readDisplay:self.screenNumber controlValue:CONTRAST]; - struct DDCReadResponse cRed = [controls readDisplay:self.screenNumber controlValue:RED_GAIN]; - struct DDCReadResponse cGreen = [controls readDisplay:self.screenNumber controlValue:GREEN_GAIN]; - struct DDCReadResponse cBlue = [controls readDisplay:self.screenNumber controlValue:BLUE_GAIN]; + struct DDCReadCommand cRed = [controls readDisplay:self.screenNumber controlValue:RED_GAIN]; + struct DDCReadCommand cGreen = [controls readDisplay:self.screenNumber controlValue:GREEN_GAIN]; + struct DDCReadCommand cBlue = [controls readDisplay:self.screenNumber controlValue:BLUE_GAIN]; self.currentBrightness = cBrightness.current_value; self.maxBrightness = cBrightness.max_value; @@ -75,7 +75,7 @@ - (void)refreshValues { - (void)ddcReadOut { for(int i=0x00; i<=255; i++){ - struct DDCReadResponse response = [controls readDisplay:self.screenNumber controlValue:i]; + struct DDCReadCommand response = [controls readDisplay:self.screenNumber controlValue:i]; NSLog(@"VCP: %x - %d / %d \n", i, response.current_value, response.max_value); } diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index df49009..db5f853 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -1,24 +1,24 @@ -/* - * ddc.c - * ddc - * - * Created by Jonathan Taylor on 07/10/2009. - * Copyright 2009 __MyCompanyName__. All rights reserved. - * - */ +// +// DDC.c +// DDC Panel +// +// Created by Jonathan Taylor on 7/10/09. +// See http://github.com/jontaylor/DDC-CI-Tools-for-OS-X +// + +#include +#include +#include +#include "DDC.h" -#include -#include -#include "ddc.h" +#define kMaxRequests 10 /* - IOFramebufferPortFromCGDisplayID based on: https://github.com/kfix/ddcctl/commit/0d66010890f99aa0972bb1478b41dda6329f52b4 Iterate IOreg's device tree to find the IOFramebuffer mach service port that corresponds to a given CGDisplayID replaces CGDisplayIOServicePort: https://developer.apple.com/library/mac/documentation/GraphicsImaging/Reference/Quartz_Services_Ref/index.html#//apple_ref/c/func/CGDisplayIOServicePort based on: https://github.com/glfw/glfw/pull/192/files */ -#include static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID) { io_iterator_t iter; @@ -55,10 +55,10 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID if (serialRef) serial = CFStringCreateCopy(NULL, serialRef); #endif if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayVendorID), (const void**)&vendorIDRef)) - success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); + success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayProductID), (const void**)&productIDRef)) - success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); + success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); IOItemCount busCount; IOFBGetI2CInterfaceCount(serv, &busCount); @@ -76,7 +76,7 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID } if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplaySerialNumber), (const void**)&serialNumberRef)) - CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); + CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); // compare IOreg's metadata to CGDisplay's metadata to infer if the IOReg's I2C monitor is the display for the given NSScreen.displayID if (CGDisplayVendorNumber(displayID) != vendorID || @@ -86,8 +86,10 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID CFRelease(info); continue; } + #ifdef DEBUG // considering this IOFramebuffer as the match for the CGDisplay, dump out its information + // compare with `make displaylist` printf("\nFramebuffer: %s\n", name); printf("%s\n", CFStringGetCStringPtr(location, kCFStringEncodingUTF8)); printf("VN:%ld PN:%ld SN:%ld", vendorID, productID, serialNumber); @@ -104,194 +106,276 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID return servicePort; } -IOI2CConnectRef display_connection(CGDirectDisplayID display_id) { - kern_return_t kr; - io_service_t framebuffer, interface; - IOOptionBits bus; - IOItemCount busCount; - - //printf("Querying for displayid: %d\n", display_id); - //framebuffer = CGDisplayIOServicePort(display_id))) // Deprecated since OSX 10.9 - framebuffer = IOFramebufferPortFromCGDisplayID(display_id); - - io_string_t path; - kr = IORegistryEntryGetPath(framebuffer, kIOServicePlane, path); - if(KERN_SUCCESS != kr) // display path find failed - return nil; - - kr = IOFBGetI2CInterfaceCount(framebuffer, &busCount ); - assert(kIOReturnSuccess == kr); - - for(bus = 0; bus < busCount; bus++){ - IOI2CConnectRef connect; - - kr = IOFBCopyI2CInterfaceForBus(framebuffer, bus, &interface); - if(kIOReturnSuccess != kr) - continue; - - kr = IOI2CInterfaceOpen(interface, kNilOptions, &connect); - - IOObjectRelease(interface); - assert(kIOReturnSuccess == kr); - if(kIOReturnSuccess != kr) - continue; - +dispatch_semaphore_t DisplayQueue(CGDirectDisplayID displayID) { + static UInt64 queueCount = 0; + static struct DDCQueue {CGDirectDisplayID id; dispatch_semaphore_t queue;} *queues = NULL; + dispatch_semaphore_t queue = NULL; + if (!queues) + queues = calloc(50, sizeof(*queues)); //FIXME: specify + UInt64 i = 0; + while (i < queueCount) + if (queues[i].id == displayID) + break; + else + i++; + if (queues[i].id == displayID) + queue = queues[i].queue; + else + queues[queueCount++] = (struct DDCQueue){displayID, (queue = dispatch_semaphore_create(1))}; + return queue; +} + +bool DisplayRequest(CGDirectDisplayID displayID, IOI2CRequest *request) { + dispatch_semaphore_t queue = DisplayQueue(displayID); + dispatch_semaphore_wait(queue, DISPATCH_TIME_FOREVER); + bool result = false; + io_service_t framebuffer; // https://developer.apple.com/reference/kernel/ioframebuffer + //if ((framebuffer = CGDisplayIOServicePort(displayID))) { // Deprecated in OSX 10.9 + if ((framebuffer = IOFramebufferPortFromCGDisplayID(displayID))) { + IOItemCount busCount; + if (IOFBGetI2CInterfaceCount(framebuffer, &busCount) == KERN_SUCCESS) { + IOOptionBits bus = 0; + while (bus < busCount) { + io_service_t interface; + if (IOFBCopyI2CInterfaceForBus(framebuffer, bus++, &interface) != KERN_SUCCESS) + continue; + + IOI2CConnectRef connect; + if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) == KERN_SUCCESS) { + result = (IOI2CSendRequest(connect, kNilOptions, request) == KERN_SUCCESS); + IOI2CInterfaceClose(connect, kNilOptions); + } + IOObjectRelease(interface); + if (result) break; + } + } IOObjectRelease(framebuffer); - return connect; } - - IOObjectRelease(framebuffer); - return nil; + if (request->replyTransactionType == kIOI2CNoTransactionType) + usleep(20000); + dispatch_semaphore_signal(queue); + return result && request->result == KERN_SUCCESS; } -int ddc_write(CGDirectDisplayID display_id, struct DDCWriteCommand* p_write) { - UInt8 data[128]; - IOI2CRequest request; - kern_return_t kr; +bool DDCWrite(CGDirectDisplayID displayID, struct DDCWriteCommand *write) { + IOI2CRequest request; + UInt8 data[128]; - IOI2CConnectRef connect = display_connection(display_id); - if(!connect) - return 0; + bzero( &request, sizeof(request)); - bzero(&request, sizeof(request)); + request.commFlags = 0; - request.commFlags = 0; - request.sendAddress = 0x6e; - request.sendTransactionType = kIOI2CSimpleTransactionType; - request.sendBuffer = (vm_address_t) &data[0]; - request.sendBytes = 7; + request.sendAddress = 0x6E; + request.sendTransactionType = kIOI2CSimpleTransactionType; + request.sendBuffer = (vm_address_t) &data[0]; + request.sendBytes = 7; data[0] = 0x51; data[1] = 0x84; data[2] = 0x03; - data[3] = (*p_write).control_id; - data[4] = 0x1; - data[5] = (*p_write).new_value; - data[6] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3] ^ data[4] ^ data[5]; - - request.replyTransactionType = kIOI2CNoTransactionType; - request.replyBytes = 0; + data[3] = write->control_id; + data[4] = (write->new_value) >> 8; + data[5] = write->new_value & 255; + data[6] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3]^ data[4] ^ data[5]; - kr = IOI2CSendRequest(connect, kNilOptions, &request); - IOI2CInterfaceClose(connect, kNilOptions); - - assert(kIOReturnSuccess == kr); - if(kIOReturnSuccess != request.result) - return 0; + request.replyTransactionType = kIOI2CNoTransactionType; + request.replyBytes = 0; - return 1; + bool result = DisplayRequest(displayID, &request); + return result; } -int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand* p_read) { - UInt8 data[128]; +bool DDCRead(CGDirectDisplayID displayID, struct DDCReadCommand *read) { IOI2CRequest request; - kern_return_t kr; - UInt8 reply_data[11]; + UInt8 reply_data[11] = {}; + bool result = false; + UInt8 data[128]; - IOI2CConnectRef connect = display_connection(display_id); - if(!connect) - return 0; - - int successful_reads = 0; - int max_reads = 10; - - for (int i=0; icontrol_id; + data[4] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3]; +#ifdef TT_SIMPLE + request.replyTransactionType = kIOI2CSimpleTransactionType; +#elif defined TT_DDC + request.replyTransactionType = kIOI2CDDCciReplyTransactionType; +#else + request.replyTransactionType = SupportedTransactionType(); +#endif + request.replyAddress = 0x6F; + request.replySubAddress = 0x51; + + request.replyBuffer = (vm_address_t) reply_data; + request.replyBytes = sizeof(reply_data); + + result = DisplayRequest(displayID, &request); + result = (result && reply_data[0] == request.sendAddress && reply_data[2] == 0x2 && reply_data[4] == read->control_id && reply_data[10] == (request.replyAddress ^ request.replySubAddress ^ reply_data[1] ^ reply_data[2] ^ reply_data[3] ^ reply_data[4] ^ reply_data[5] ^ reply_data[6] ^ reply_data[7] ^ reply_data[8] ^ reply_data[9])); + + if (result) { // checksum is ok + if (i > 1) { + printf("D: Tries required to get data: %d \n", i); + } + break; + } + + if (request.result == kIOReturnUnsupportedMode) + printf("E: Unsupported Transaction Type! \n"); + + // reset values and return 0, if data reading fails + if (i >= kMaxRequests) { + read->max_value = 0; + read->current_value = 0; + printf("E: No data after %d tries! \n", i); + return 0; + } + + usleep(40000); // 40msec -> See DDC/CI Vesa Standard - 4.4.1 Communication Error Recovery } - - return 1; + read->max_value = reply_data[7]; + read->current_value = reply_data[9]; + return result; } -void EDIDRead(CGDirectDisplayID display_id, struct EDID* edid) { - kern_return_t kr; - IOI2CConnectRef connect; - IOI2CRequest request; - UInt8 data[128]; +int SupportedTransactionType() { + /* + With my setup (Intel HD4600 via displaylink to 'DELL U2515H') the original app failed to read ddc and freezes my system. + This happens because AppleIntelFramebuffer do not support kIOI2CDDCciReplyTransactionType. + So this version comes with a reworked ddc read function to detect the correct TransactionType. + --SamanVDR 2016 + */ - if(!(connect = display_connection(display_id))) - return; + kern_return_t kr; + io_iterator_t io_objects; + io_service_t io_service; - bzero( &request, sizeof(request) ); + kr = IOServiceGetMatchingServices(kIOMasterPortDefault, + IOServiceNameMatching("IOFramebufferI2CInterface"), &io_objects); - request.commFlags = 0; - request.sendAddress = 0xA0; - request.sendTransactionType = kIOI2CSimpleTransactionType; - request.sendBuffer = (vm_address_t)&data[0]; - request.sendBytes = 0x01; - data[0] = 0x00; - - request.replyAddress = 0xA1; - request.replyTransactionType = kIOI2CSimpleTransactionType; - request.replyBuffer = (vm_address_t)&data[0]; - request.replyBytes = sizeof(data); - bzero( &data[0], request.replyBytes ); + if (kr != KERN_SUCCESS) { + printf("E: Fatal - No matching service! \n"); + return 0; + } - kr = IOI2CSendRequest(connect, kNilOptions, &request); - assert(kIOReturnSuccess == kr); - if(kIOReturnSuccess != request.result) - return; + UInt64 supportedType = 0; - if(edid) memcpy(edid, &data, 128); + while((io_service = IOIteratorNext(io_objects)) != MACH_PORT_NULL) + { + CFMutableDictionaryRef service_properties; + CFIndex types = 0; + CFNumberRef typesRef; + + kr = IORegistryEntryCreateCFProperties(io_service, &service_properties, kCFAllocatorDefault, kNilOptions); + if (kr == KERN_SUCCESS) + { + if (CFDictionaryGetValueIfPresent(service_properties, CFSTR(kIOI2CTransactionTypesKey), (const void**)&typesRef)) + CFNumberGetValue(typesRef, kCFNumberCFIndexType, &types); + + /* + We want DDCciReply but Simple is better than No-thing. + Combined and DisplayPortNative are not useful in our case. + */ + if (types) { +#ifdef DEBUG + printf("\nD: IOI2CTransactionTypes: 0x%02lx (%ld)\n", types, types); + + // kIOI2CNoTransactionType = 0 + if ( 0 == ((1 << kIOI2CNoTransactionType) & (UInt64)types)) { + printf("E: IOI2CNoTransactionType unsupported \n"); + } else { + printf("D: IOI2CNoTransactionType supported \n"); + supportedType = kIOI2CNoTransactionType; + } + + // kIOI2CSimpleTransactionType = 1 + if ( 0 == ((1 << kIOI2CSimpleTransactionType) & (UInt64)types)) { + printf("E: IOI2CSimpleTransactionType unsupported \n"); + } else { + printf("D: IOI2CSimpleTransactionType supported \n"); + supportedType = kIOI2CSimpleTransactionType; + } + + // kIOI2CDDCciReplyTransactionType = 2 + if ( 0 == ((1 << kIOI2CDDCciReplyTransactionType) & (UInt64)types)) { + printf("E: IOI2CDDCciReplyTransactionType unsupported \n"); + } else { + printf("D: IOI2CDDCciReplyTransactionType supported \n"); + supportedType = kIOI2CDDCciReplyTransactionType; + } + + // kIOI2CCombinedTransactionType = 3 + if ( 0 == ((1 << kIOI2CCombinedTransactionType) & (UInt64)types)) { + printf("E: IOI2CCombinedTransactionType unsupported \n"); + } else { + printf("D: IOI2CCombinedTransactionType supported \n"); + //supportedType = kIOI2CCombinedTransactionType; + } + + // kIOI2CDisplayPortNativeTransactionType = 4 + if ( 0 == ((1 << kIOI2CDisplayPortNativeTransactionType) & (UInt64)types)) { + printf("E: IOI2CDisplayPortNativeTransactionType unsupported\n"); + } else { + printf("D: IOI2CDisplayPortNativeTransactionType supported \n"); + //supportedType = kIOI2CDisplayPortNativeTransactionType; + } +#else + // kIOI2CSimpleTransactionType = 1 + if ( 0 != ((1 << kIOI2CSimpleTransactionType) & (UInt64)types)) { + supportedType = kIOI2CSimpleTransactionType; + } + + // kIOI2CDDCciReplyTransactionType = 2 + if ( 0 != ((1 << kIOI2CDDCciReplyTransactionType) & (UInt64)types)) { + supportedType = kIOI2CDDCciReplyTransactionType; + } +#endif + } else printf("E: Fatal - No supported Transaction Types! \n"); + + CFRelease(service_properties); + } + + IOObjectRelease(io_service); + + // Mac OS offers three framebuffer devices, but we can leave here + if (supportedType > 0) return supportedType; + } + return supportedType; +} + + +bool EDIDTest(CGDirectDisplayID displayID, struct EDID *edid) { + IOI2CRequest request = {}; + UInt8 data[128] = {}; + request.sendAddress = 0xA0; + request.sendTransactionType = kIOI2CSimpleTransactionType; + request.sendBuffer = (vm_address_t) data; + request.sendBytes = 0x01; + data[0] = 0x00; + request.replyAddress = 0xA1; + request.replyTransactionType = kIOI2CSimpleTransactionType; + request.replyBuffer = (vm_address_t) data; + request.replyBytes = sizeof(data); + if (!DisplayRequest(displayID, &request)) return false; + if (edid) memcpy(edid, &data, 128); UInt32 i = 0; UInt8 sum = 0; - while(i < request.replyBytes) { - if(i % 128 == 0) { - if(sum)break; + while (i < request.replyBytes) { + if (i % 128 == 0) { + if (sum) break; sum = 0; } sum += data[i++]; } - - IOI2CInterfaceClose(connect, kNilOptions); + return !sum; } - diff --git a/BrightnessMenulet/ddc.h b/BrightnessMenulet/ddc.h index 7c5fc6c..58a5eed 100755 --- a/BrightnessMenulet/ddc.h +++ b/BrightnessMenulet/ddc.h @@ -1,30 +1,29 @@ -/* - * ddc.h - * ddc - * - * Created by Jonathan Taylor on 07/10/2009. - * Copyright 2009 __MyCompanyName__. All rights reserved. - * - */ +// +// DDC.h +// DDC Panel +// +// Created by Jonathan Taylor on 7/10/09. +// See ftp://ftp.cis.nctu.edu.tw/pub/csie/Software/X11/private/VeSaSpEcS/VESA_Document_Center_Monitor_Interface/mccsV3.pdf +// See http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf +// See ftp://ftp.cis.nctu.edu.tw/pub/csie/Software/X11/private/VeSaSpEcS/VESA_Document_Center_Monitor_Interface/EEDIDrAr2.pdf +// -#ifdef __cplusplus -extern "C" { -#endif +#ifndef DDC_Panel_DDC_h +#define DDC_Panel_DDC_h -#include -#include #include #define RESET 0x04 #define RESET_BRIGHTNESS_AND_CONTRAST 0x05 #define RESET_GEOMETRY 0x06 -#define RESET_COLOR 0x08 +#define RESET_COLOR 0x08 #define BRIGHTNESS 0x10 //OK #define CONTRAST 0x12 //OK +#define COLOR_PRESET_A 0x14 // dell u2515h -> Presets: 4 = 5000K, 5 = 6500K, 6 = 7500K, 8 = 9300K, 9 = 10000K, 11 = 5700K, 12 = Custom Color #define RED_GAIN 0x16 #define GREEN_GAIN 0x18 #define BLUE_GAIN 0x1A -#define AUTO_SIZE_CENTER 0x1E +#define AUTO_SIZE_CENTER 0x1E #define WIDTH 0x22 #define HEIGHT 0x32 #define VERTICAL_POS 0x30 @@ -47,39 +46,35 @@ extern "C" { #define RED_BLACK_LEVEL 0x6C #define GREEN_BLACK_LEVEL 0x6E #define BLUE_BLACK_LEVEL 0x70 - -#define SETTINGS 0xB0 //unsure on this one -#define ON_SCREEN_DISPLAY 0xCA +#define ORIENTATION 0xAA +#define AUDIO_MUTE 0x8D +#define SETTINGS 0xB0 //unsure on this one +#define ON_SCREEN_DISPLAY 0xCA // read only -> returns '1' (OSD closed) or '2' (OSD active) #define OSD_LANGUAGE 0xCC #define DPMS 0xD6 -#define MAGIC_BRIGHT 0xDC //unsure +#define COLOR_PRESET_B 0xDC // dell u2515h -> Presets: 0 = Standard, 2 = Multimedia, 3 = Movie, 5 = Game #define VCP_VERSION 0xDF -#define COLOR_PRESET 0xE0 -#define POWER_CONTROL 0xE1 - +#define COLOR_PRESET_C 0xE0 // dell u2515h -> Brightness on/off (0 or 1) +#define POWER_CONTROL 0xE1 #define TOP_LEFT_SCREEN_PURITY 0xE8 #define TOP_RIGHT_SCREEN_PURITY 0xE9 #define BOTTOM_LEFT_SCREEN_PURITY 0xE8 -#define BOTTOM_RIGHT_SCREEN_PURITY 0xEB - -struct DDCWriteCommand { +#define BOTTOM_RIGHT_SCREEN_PURITY 0xEB + + +struct DDCWriteCommand +{ UInt8 control_id; UInt8 new_value; }; - -struct DDCReadResponse { + +struct DDCReadCommand +{ + UInt8 control_id; UInt8 max_value; UInt8 current_value; }; -struct DDCReadCommand { - UInt8 control_id; - size_t reply_bytes; - unsigned char* reply_buffer; - struct DDCReadResponse response; -}; - -// EDID struct credits to https://github.com/kfix/ddcctl struct EDID { UInt64 header : 64; UInt8 : 1; @@ -90,13 +85,14 @@ struct EDID { UInt8 year : 8; UInt8 versionmajor : 8; UInt8 versionminor : 8; - UInt8 digitalinput : 1; - union inputbitmap { + union videoinput { struct digitalinput { + UInt8 type : 1; UInt8 : 6; UInt8 dfp : 1; } digital; struct analoginput { + UInt8 type : 1; UInt8 synclevels : 2; UInt8 pedestal : 1; UInt8 separate : 1; @@ -104,7 +100,7 @@ struct EDID { UInt8 green : 1; UInt8 serrated : 1; } analog; - }; + } videoinput; UInt8 maxh : 8; UInt8 maxv : 8; UInt8 gamma : 8; @@ -189,16 +185,8 @@ struct EDID { UInt8 interlaced : 1; UInt8 stereo : 2; UInt8 synctype : 2; - union sync { - struct analogsync { - UInt8 serrated : 1; - UInt8 syncall : 1; - } analog; - struct digitalsync { - UInt8 vsync : 1; - UInt8 hsync : 1; - } digital; - }; + UInt8 vsyncpol_serrated: 1; + UInt8 hsyncpol_syncall: 1; UInt8 twowaystereo : 1; } timing; struct text { @@ -248,10 +236,8 @@ struct EDID { UInt8 checksum : 8; }; -int ddc_write(CGDirectDisplayID display_id, struct DDCWriteCommand * p_write); -int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand * p_read); -void EDIDRead(CGDirectDisplayID display_id, struct EDID *edid); - -#ifdef __cplusplus -} -#endif \ No newline at end of file +bool DDCWrite(CGDirectDisplayID displayID, struct DDCWriteCommand *write); +bool DDCRead(CGDirectDisplayID displayID, struct DDCReadCommand *read); +bool EDIDTest(CGDirectDisplayID displayID, struct EDID *edid); +int SupportedTransactionType(); +#endif From 3080388ac8e935a807e72b6999405898bd2af124 Mon Sep 17 00:00:00 2001 From: Peter Wagenet Date: Tue, 10 Jan 2017 07:47:55 -0800 Subject: [PATCH 13/15] Handle EDIDTest failure --- BrightnessMenulet/DDCControls.m | 59 +++++++++++++++++---------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/BrightnessMenulet/DDCControls.m b/BrightnessMenulet/DDCControls.m index 7ff8310..24e99ba 100644 --- a/BrightnessMenulet/DDCControls.m +++ b/BrightnessMenulet/DDCControls.m @@ -55,39 +55,42 @@ - (void)refreshScreens { // Fetch Monitor info via EDID struct EDID edid = {}; - EDIDTest([screenNumber unsignedIntegerValue], &edid); + if (EDIDTest([screenNumber unsignedIntegerValue], &edid)) { - NSString* name; - NSString* serial; - for (NSValue *value in @[[NSValue valueWithPointer:&edid.descriptor1], [NSValue valueWithPointer:&edid.descriptor2], [NSValue valueWithPointer:&edid.descriptor3], [NSValue valueWithPointer:&edid.descriptor4]]) { - union descriptor *des = value.pointerValue; - switch (des->text.type) { - case 0xFF: - serial = [self EDIDString:des->text.data]; - break; - case 0xFC: - name = [self EDIDString:des->text.data]; - break; + NSString* name; + NSString* serial; + for (NSValue *value in @[[NSValue valueWithPointer:&edid.descriptor1], [NSValue valueWithPointer:&edid.descriptor2], [NSValue valueWithPointer:&edid.descriptor3], [NSValue valueWithPointer:&edid.descriptor4]]) { + union descriptor *des = value.pointerValue; + switch (des->text.type) { + case 0xFF: + serial = [self EDIDString:des->text.data]; + break; + case 0xFC: + name = [self EDIDString:des->text.data]; + break; + } } - } - // don't want to manage invalid screen or integrated LCD - if(!name || [name isEqualToString:@"Color LCD"] || [name isEqualToString:@"iMac"]) continue; - - // skipping screen's, that don't support data reading - struct DDCReadCommand read_command = (struct DDCReadCommand){.control_id = BRIGHTNESS}; - if(DDCRead([screenNumber unsignedIntegerValue] , &read_command) != 1) { - NSLog(@"Reading data from display:%@ failed ", screenNumber); - NSLog(@"... skipping %@ ", name); - continue; - } + // don't want to manage invalid screen or integrated LCD + if(!name || [name isEqualToString:@"Color LCD"] || [name isEqualToString:@"iMac"]) continue; + + // skipping screen's, that don't support data reading + struct DDCReadCommand read_command = (struct DDCReadCommand){.control_id = BRIGHTNESS}; + if(DDCRead([screenNumber unsignedIntegerValue] , &read_command) != 1) { + NSLog(@"Reading data from display:%@ failed ", screenNumber); + NSLog(@"... skipping %@ ", name); + continue; + } - // Build screen instance - NSLog(@"DDCControls: Found %@ - %@", name, screenNumber); - Screen* screen = [[Screen alloc] initWithModel:name screenID:[screenNumber unsignedIntegerValue] serial:serial]; - [screen refreshValues]; + // Build screen instance + NSLog(@"DDCControls: Found %@ - %@", name, screenNumber); + Screen* screen = [[Screen alloc] initWithModel:name screenID:[screenNumber unsignedIntegerValue] serial:serial]; + [screen refreshValues]; - [newScreens addObject:screen]; + [newScreens addObject:screen]; + } else { + NSLog(@"Failed to poll display: %@", screenNumber); + } } _screens = [newScreens copy]; From 835cc92a24e125e728dee08dc74303842bec98e3 Mon Sep 17 00:00:00 2001 From: Marc Date: Tue, 10 Jan 2017 20:09:03 +0100 Subject: [PATCH 14/15] small additions --- BrightnessMenulet/.DS_Store | Bin 6148 -> 6148 bytes BrightnessMenulet/AppDelegate.m | 3 + BrightnessMenulet/PreferencesController.m | 5 +- BrightnessMenulet/ddc.c | 24 +++--- BrightnessMenulet/ddc.h | 95 +++++++++++----------- 5 files changed, 67 insertions(+), 60 deletions(-) diff --git a/BrightnessMenulet/.DS_Store b/BrightnessMenulet/.DS_Store index 7b6d0b58bdda2cd26cb0e0deefdc613e2f4c61ff..644b91adcc1f1b7551a381832a36fd8a3154e626 100644 GIT binary patch delta 65 zcmZoMXffCj!o)Q7%j5-2HaspaDWxUJ3=CIlCRt9N$K=KWW!Uf+WEch~=jRqMfB?&d O$@7`4H|H@=69EAFj}`m? delta 65 zcmZoMXffCj!o;+8)8qwAHaspaDWxUJ3=Eg@pBkI9V%%CO-t$S@2}&d)7i00CBk O$@7`4H|H@=69E7P0Ts^x diff --git a/BrightnessMenulet/AppDelegate.m b/BrightnessMenulet/AppDelegate.m index f1e8a6c..b2452e8 100644 --- a/BrightnessMenulet/AppDelegate.m +++ b/BrightnessMenulet/AppDelegate.m @@ -29,6 +29,9 @@ - (void)awakeFromNib NSLog(@"... is already running!"); [NSApp terminate:nil]; } + + // just to be shure, that we didn't miss something super fancy + [super awakeFromNib]; } diff --git a/BrightnessMenulet/PreferencesController.m b/BrightnessMenulet/PreferencesController.m index 0598b9b..95357e5 100644 --- a/BrightnessMenulet/PreferencesController.m +++ b/BrightnessMenulet/PreferencesController.m @@ -60,7 +60,10 @@ - (void)showWindow { if(!_preferenceWindow){ NSLog(@"PreferencesController: Pref Window alloc"); [[NSBundle mainBundle] loadNibNamed:@"Preferences" owner:self topLevelObjects:nil]; - + + // Save the last location (to fix a xcode bug, we have to set this here) + _preferenceWindow.frameAutosaveName = @"PreferencesWindowLocation"; + _preferenceWindow.delegate = self; NSNumberFormatter* decFormater = [[NSNumberFormatter alloc] init]; diff --git a/BrightnessMenulet/ddc.c b/BrightnessMenulet/ddc.c index df49009..348b55d 100755 --- a/BrightnessMenulet/ddc.c +++ b/BrightnessMenulet/ddc.c @@ -55,10 +55,10 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID if (serialRef) serial = CFStringCreateCopy(NULL, serialRef); #endif if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayVendorID), (const void**)&vendorIDRef)) - success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); + success = CFNumberGetValue(vendorIDRef, kCFNumberCFIndexType, &vendorID); if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayProductID), (const void**)&productIDRef)) - success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); + success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); IOItemCount busCount; IOFBGetI2CInterfaceCount(serv, &busCount); @@ -76,7 +76,7 @@ static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID } if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplaySerialNumber), (const void**)&serialNumberRef)) - CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); + CFNumberGetValue(serialNumberRef, kCFNumberCFIndexType, &serialNumber); // compare IOreg's metadata to CGDisplay's metadata to infer if the IOReg's I2C monitor is the display for the given NSScreen.displayID if (CGDisplayVendorNumber(displayID) != vendorID || @@ -156,10 +156,10 @@ int ddc_write(CGDirectDisplayID display_id, struct DDCWriteCommand* p_write) { bzero(&request, sizeof(request)); request.commFlags = 0; - request.sendAddress = 0x6e; + request.sendAddress = 0x6E; request.sendTransactionType = kIOI2CSimpleTransactionType; - request.sendBuffer = (vm_address_t) &data[0]; - request.sendBytes = 7; + request.sendBuffer = (vm_address_t) &data[0]; + request.sendBytes = 7; data[0] = 0x51; data[1] = 0x84; @@ -211,7 +211,7 @@ int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand* p_read) { data[4] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3]; - request.replyAddress = 0x6f; + request.replyAddress = 0x6F; request.replyTransactionType = kIOI2CSimpleTransactionType; request.replyBuffer = (vm_address_t) &reply_data[0] ; @@ -223,7 +223,7 @@ int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand* p_read) { bzero(&reply_data[0], request.replyBytes); kr = IOI2CSendRequest(connect, kNilOptions, &request); - calculated_checksum = 0x6f ^ 0x51 ^ reply_data[1] ^ reply_data[2] ^ reply_data[3] ^ reply_data[4]^ reply_data[5]^ reply_data[6]^ reply_data[7]^ reply_data[8]^ reply_data[9]; + calculated_checksum = 0x6F ^ 0x51 ^ reply_data[1] ^ reply_data[2] ^ reply_data[3] ^ reply_data[4]^ reply_data[5]^ reply_data[6]^ reply_data[7]^ reply_data[8]^ reply_data[9]; if ((reply_data[10] == calculated_checksum) && reply_data[4] == data[3] ) { successful_reads++; @@ -243,10 +243,10 @@ int ddc_read(CGDirectDisplayID display_id, struct DDCReadCommand* p_read) { (*p_read).response.current_value = reply_data[9]; assert(kIOReturnSuccess == kr); - if(kIOReturnSuccess != request.result) { - printf("Error getting result\n"); - return 0; - } + if (kIOReturnSuccess != request.result) { + printf("Error getting result\n"); + return 0; + } return 1; } diff --git a/BrightnessMenulet/ddc.h b/BrightnessMenulet/ddc.h index 7c5fc6c..8c533c0 100755 --- a/BrightnessMenulet/ddc.h +++ b/BrightnessMenulet/ddc.h @@ -15,52 +15,53 @@ extern "C" { #include #include -#define RESET 0x04 -#define RESET_BRIGHTNESS_AND_CONTRAST 0x05 -#define RESET_GEOMETRY 0x06 -#define RESET_COLOR 0x08 -#define BRIGHTNESS 0x10 //OK -#define CONTRAST 0x12 //OK -#define RED_GAIN 0x16 -#define GREEN_GAIN 0x18 -#define BLUE_GAIN 0x1A -#define AUTO_SIZE_CENTER 0x1E -#define WIDTH 0x22 -#define HEIGHT 0x32 -#define VERTICAL_POS 0x30 -#define HORIZONTAL_POS 0x20 -#define PINCUSHION_AMP 0x24 -#define PINCUSHION_PHASE 0x42 -#define KEYSTONE_BALANCE 0x40 -#define PINCUSHION_BALANCE 0x26 -#define TOP_PINCUSHION_AMP 0x46 -#define TOP_PINCUSHION_BALANCE 0x48 -#define BOTTOM_PINCUSHION_AMP 0x4A -#define BOTTOM_PINCUSHION_BALANCE 0x4C -#define VERTICAL_LINEARITY 0x3A -#define VERTICAL_LINEARITY_BALANCE 0x3C -#define HORIZONTAL_STATIC_CONVERGENCE 0x28 -#define VERTICAL_STATIC_CONVERGENCE 0x28 -#define MOIRE_CANCEL 0x56 -#define INPUT_SOURCE 0x60 -#define AUDIO_SPEAKER_VOLUME 0x62 -#define RED_BLACK_LEVEL 0x6C -#define GREEN_BLACK_LEVEL 0x6E -#define BLUE_BLACK_LEVEL 0x70 - -#define SETTINGS 0xB0 //unsure on this one -#define ON_SCREEN_DISPLAY 0xCA -#define OSD_LANGUAGE 0xCC -#define DPMS 0xD6 -#define MAGIC_BRIGHT 0xDC //unsure -#define VCP_VERSION 0xDF -#define COLOR_PRESET 0xE0 -#define POWER_CONTROL 0xE1 - -#define TOP_LEFT_SCREEN_PURITY 0xE8 -#define TOP_RIGHT_SCREEN_PURITY 0xE9 -#define BOTTOM_LEFT_SCREEN_PURITY 0xE8 -#define BOTTOM_RIGHT_SCREEN_PURITY 0xEB +#define RESET 0x04 +#define RESET_BRIGHTNESS_AND_CONTRAST 0x05 +#define RESET_GEOMETRY 0x06 +#define RESET_COLOR 0x08 +#define BRIGHTNESS 0x10 +#define CONTRAST 0x12 +#define COLOR_PRESET_A 0x14 // dell u2515h -> Presets: 4 = 5000K, 5 = 6500K, 6 = 7500K, 8 = 9300K, 9 = 10000K, 11 = 5700K, 12 = Custom Color +#define RED_GAIN 0x16 +#define GREEN_GAIN 0x18 +#define BLUE_GAIN 0x1A +#define AUTO_SIZE_CENTER 0x1E +#define WIDTH 0x22 +#define HEIGHT 0x32 +#define VERTICAL_POS 0x30 +#define HORIZONTAL_POS 0x20 +#define PINCUSHION_AMP 0x24 +#define PINCUSHION_PHASE 0x42 +#define KEYSTONE_BALANCE 0x40 +#define PINCUSHION_BALANCE 0x26 +#define TOP_PINCUSHION_AMP 0x46 +#define TOP_PINCUSHION_BALANCE 0x48 +#define BOTTOM_PINCUSHION_AMP 0x4A +#define BOTTOM_PINCUSHION_BALANCE 0x4C +#define VERTICAL_LINEARITY 0x3A +#define VERTICAL_LINEARITY_BALANCE 0x3C +#define HORIZONTAL_STATIC_CONVERGENCE 0x28 +#define VERTICAL_STATIC_CONVERGENCE 0x38 +#define MOIRE_CANCEL 0x56 +#define INPUT_SOURCE 0x60 +#define AUDIO_SPEAKER_VOLUME 0x62 +#define RED_BLACK_LEVEL 0x6C +#define GREEN_BLACK_LEVEL 0x6E +#define BLUE_BLACK_LEVEL 0x70 +#define ORIENTATION 0xAA +#define AUDIO_MUTE 0x8D +#define SETTINGS 0xB0 // unsure +#define ON_SCREEN_DISPLAY 0xCA // read only -> returns '1' (OSD closed) or '2' (OSD active) +#define OSD_LANGUAGE 0xCC // read only +#define DPMS 0xD6 +#define COLOR_PRESET_B 0xDC // dell u2515h -> Presets: 0 = Standard, 2 = Multimedia, 3 = Movie, 5 = Game +#define VCP_VERSION 0xDF +#define COLOR_PRESET_C 0xE0 // dell u2515h -> Brightness on/off (0 or 1) +#define POWER_CONTROL 0xE1 +#define TOP_LEFT_SCREEN_PURITY 0xE8 +#define TOP_RIGHT_SCREEN_PURITY 0xE9 +#define BOTTOM_LEFT_SCREEN_PURITY 0xEA +#define BOTTOM_RIGHT_SCREEN_PURITY 0xEB struct DDCWriteCommand { UInt8 control_id; @@ -254,4 +255,4 @@ void EDIDRead(CGDirectDisplayID display_id, struct EDID *edid); #ifdef __cplusplus } -#endif \ No newline at end of file +#endif From f33d03187e64f453f1ed23bd3adfe68d721299d2 Mon Sep 17 00:00:00 2001 From: Saman-VDR Date: Tue, 10 Jan 2017 20:47:06 +0100 Subject: [PATCH 15/15] Delete .DS_Store --- BrightnessMenulet/.DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 BrightnessMenulet/.DS_Store diff --git a/BrightnessMenulet/.DS_Store b/BrightnessMenulet/.DS_Store deleted file mode 100644 index 644b91adcc1f1b7551a381832a36fd8a3154e626..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK!AiqG5Pe%KRJ`;kc*z$CJ$efv9zEp;gho=O&4${D2M_rM!T<8$r}zQB*&U>% zdI=(_%#_);yP28Hz9qXI04{6G888Jfp$d*p*nA`MTy#%HI&(~PA;tn5)VRe1eUsIDNCz3dM!DGky}x;nIlK8Ux0_E(1q#IFgW1@*U3i4fHCl|7;xjf$TKcU z@7BG=$z7XJZ>S<-*G2qtVJBS0w3Vy)j2eXViFAmmVi%D!6#F9(Xt2f@_)`Wx0TlyU AZU6uP