diff --git a/NativeDisplayBrightness.xcodeproj/project.pbxproj b/NativeDisplayBrightness.xcodeproj/project.pbxproj index ba50f8d..60022a2 100644 --- a/NativeDisplayBrightness.xcodeproj/project.pbxproj +++ b/NativeDisplayBrightness.xcodeproj/project.pbxproj @@ -11,12 +11,11 @@ 9DBE37541DB7990900ABE422 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBE37531DB7990900ABE422 /* main.m */; }; 9DBE37561DB7990900ABE422 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9DBE37551DB7990900ABE422 /* Assets.xcassets */; }; 9DBE37591DB7990900ABE422 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9DBE37571DB7990900ABE422 /* MainMenu.xib */; }; - 9DBE37621DB7996100ABE422 /* DDC.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DBE37601DB7996100ABE422 /* DDC.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; + 9DBE37621DB7996100ABE422 /* DDC.c in Sources */ = {isa = PBXBuildFile; fileRef = 9DBE37601DB7996100ABE422 /* DDC.c */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 9D1F75421DBD44310039345A /* OSD.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSD.h; sourceTree = ""; }; - 9D1F75431DBD48310039345A /* CoreGraphicsPriv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreGraphicsPriv.h; sourceTree = ""; }; 9DBE374C1DB7990900ABE422 /* NativeDisplayBrightness.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NativeDisplayBrightness.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9DBE374F1DB7990900ABE422 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 9DBE37501DB7990900ABE422 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -24,11 +23,11 @@ 9DBE37551DB7990900ABE422 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9DBE37581DB7990900ABE422 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 9DBE375A1DB7990900ABE422 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9DBE37601DB7996100ABE422 /* DDC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = DDC.m; sourceTree = ""; }; + 9DBE37601DB7996100ABE422 /* DDC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = DDC.c; sourceTree = ""; }; 9DBE37611DB7996100ABE422 /* DDC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DDC.h; sourceTree = ""; }; 9DBE37631DB79A4000ABE422 /* BezelServices.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BezelServices.h; sourceTree = ""; }; - 9DBE37641DB7A87200ABE422 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = ""; }; - 9DBE37661DB7A9D600ABE422 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; + 9DBE37641DB7A87200ABE422 /* Readme.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = Readme.md; sourceTree = SOURCE_ROOT; }; + 9DBE37661DB7A9D600ABE422 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -66,14 +65,13 @@ 9DBE37551DB7990900ABE422 /* Assets.xcassets */, 9DBE37571DB7990900ABE422 /* MainMenu.xib */, 9DBE375A1DB7990900ABE422 /* Info.plist */, - 9DBE37601DB7996100ABE422 /* DDC.m */, + 9DBE37601DB7996100ABE422 /* DDC.c */, 9DBE37641DB7A87200ABE422 /* Readme.md */, 9DBE37661DB7A9D600ABE422 /* License.txt */, 9DBE37611DB7996100ABE422 /* DDC.h */, 9DBE37521DB7990900ABE422 /* Supporting Files */, 9DBE37631DB79A4000ABE422 /* BezelServices.h */, 9D1F75421DBD44310039345A /* OSD.h */, - 9D1F75431DBD48310039345A /* CoreGraphicsPriv.h */, ); path = NativeDisplayBrightness; sourceTree = ""; @@ -117,7 +115,7 @@ TargetAttributes = { 9DBE374B1DB7990900ABE422 = { CreatedOnToolsVersion = 8.0; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -156,7 +154,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 9DBE37621DB7996100ABE422 /* DDC.m in Sources */, + 9DBE37621DB7996100ABE422 /* DDC.c in Sources */, 9DBE37541DB7990900ABE422 /* main.m in Sources */, 9DBE37511DB7990900ABE422 /* AppDelegate.m in Sources */, ); @@ -267,11 +265,15 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = NativeDisplayBrightness/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.bensge.NativeDisplayBrightness; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Debug; }; @@ -279,11 +281,16 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "Developer ID Application"; + CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 7V6VCE4Z4V; INFOPLIST_FILE = NativeDisplayBrightness/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; PRODUCT_BUNDLE_IDENTIFIER = com.bensge.NativeDisplayBrightness; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; }; name = Release; }; diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index 34d7721..5a36988 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -11,32 +11,96 @@ #import "BezelServices.h" #import "OSD.h" #include -@import Carbon; + +#pragma mark - Key codes of special keys + +// Extract from Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h +/* keycodes for keys that are independent of keyboard layout*/ +enum { + 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 +}; #pragma mark - constants -static NSString *brightnessValuePreferenceKey = @"brightness"; -static const float brightnessStep = 100/16.f; +static NSString *const kDisplaysBrightnessDefaultsKey = @"displays-brightness"; +static const int brightnessStepsCount = 16; +static const int brightnessSubstepsPerStep = 4; +static const int brightnessSubstepsCount = brightnessStepsCount * brightnessSubstepsPerStep; #pragma mark - variables -void *(*_BSDoGraphicWithMeterAndTimeout)(CGDirectDisplayID arg0, BSGraphic arg1, int arg2, float v, int timeout) = NULL; +static void *(*_BSDoGraphicWithMeterAndTimeout)(CGDirectDisplayID arg0, BSGraphic arg1, int arg2, float v, int timeout) = NULL; #pragma mark - functions -void set_control(CGDirectDisplayID cdisplay, uint control_id, uint new_value) +static BOOL set_control(CGDirectDisplayID display_id, uint control_id, uint new_value) { struct DDCWriteCommand command; command.control_id = control_id; command.new_value = new_value; - if (!DDCWrite(cdisplay, &command)){ - NSLog(@"E: Failed to send DDC command!"); + BOOL isCommandOk = DDCWrite(display_id, &command); + + if (! isCommandOk){ + NSLog(@"E: Failed to send DDCWrite command to display %u!", display_id); } + + return isCommandOk; } +static BOOL get_control(CGDirectDisplayID display_id, uint control_id, uint* current_value, uint* max_value) +{ + struct DDCReadCommand command = {.control_id = control_id, .max_value = 0, .current_value = 0 }; + BOOL isCommandOk = DDCRead(display_id, &command); + + if (isCommandOk) { + if (current_value != nil) { + *current_value = command.current_value; + } + + if (max_value != nil) { + *max_value = command.max_value; + } + } + else { + NSLog(@"E: Failed to send DDCRead command to display %u!", display_id); + } + return isCommandOk; +} -CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, +static CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) @@ -53,17 +117,30 @@ CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, return event; } +static void showBrightnessLevelPaneOnDisplay (uint brightnessLevelInSubsteps, CGDirectDisplayID displayId) +{ + if (_BSDoGraphicWithMeterAndTimeout != NULL) + { + // El Capitan and probably older systems + _BSDoGraphicWithMeterAndTimeout(displayId, BSGraphicBacklightMeter, 0x0, (float)brightnessLevelInSubsteps/(float)brightnessSubstepsCount, 1); + } + else { + // Sierra+ + [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:displayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)brightnessLevelInSubsteps totalChiclets:brightnessSubstepsCount locked:NO]; + } + +} + + #pragma mark - AppDelegate @interface AppDelegate () @property (weak) IBOutlet NSWindow *window; -@property (nonatomic) float brightness; @property (strong, nonatomic) dispatch_source_t signalHandlerSource; @end @implementation AppDelegate -@synthesize brightness=_brightness; - (BOOL)_loadBezelServices { @@ -92,31 +169,25 @@ - (void)_configureLoginItem LSSharedFileListInsertItemURL(loginItemsListRef, kLSSharedFileListItemLast, NULL, NULL, (__bridge CFURLRef)bundleURL, (__bridge CFDictionaryRef)properties,NULL); } -- (void)_checkTrusted -{ - BOOL isTrusted = AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString *)kAXTrustedCheckOptionPrompt: @true}); - NSLog(@"istrusted: %i",isTrusted); -} - - (void)_registerGlobalKeyboardEvents { - [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown | NSEventMaskKeyUp handler:^(NSEvent *_Nonnull event) { - //NSLog(@"event!!"); - if (event.keyCode == kVK_F1) - { - if (event.type == NSEventTypeKeyDown) - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self decreaseBrightness]; - }); - } - } - else if (event.keyCode == kVK_F2) + [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent *_Nonnull event) { + if (event.type == NSEventTypeKeyDown) { - if (event.type == NSEventTypeKeyDown) + BOOL isOptionModifierPressed = (event.modifierFlags & NSAlternateKeyMask) != 0; + BOOL isShiftModifierPressed = (event.modifierFlags & NSShiftKeyMask) != 0; + + if ((event.keyCode == kVK_F1) || (event.keyCode == kVK_F2)) { + // Screen brightness adjustment + int brightnessDelta = isOptionModifierPressed ? 1 : brightnessSubstepsPerStep; + if (event.keyCode == kVK_F1) { + // F1 = decrease broghtness + brightnessDelta = -brightnessDelta; + } + dispatch_async(dispatch_get_main_queue(), ^{ - [self increaseBrightness]; + [self incrementScreenBrightnessWithStep: brightnessDelta inAllScreens:isShiftModifierPressed]; }); } } @@ -135,21 +206,6 @@ - (void)_registerGlobalKeyboardEvents CGEventTapEnable(eventTap, true); } -- (void)_saveBrightness -{ - [[NSUserDefaults standardUserDefaults] setFloat:self.brightness forKey:brightnessValuePreferenceKey]; -} - -- (void)_loadBrightness -{ - [[NSUserDefaults standardUserDefaults] registerDefaults:@{ - brightnessValuePreferenceKey: @(8*brightnessStep) - }]; - - _brightness = [[NSUserDefaults standardUserDefaults] floatForKey:brightnessValuePreferenceKey]; - NSLog(@"Loaded value: %f",_brightness); -} - - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { if (![self _loadBezelServices]) @@ -157,10 +213,24 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification [self _loadOSDFramework]; } [self _configureLoginItem]; - [self _checkTrusted]; - [self _registerGlobalKeyboardEvents]; - [self _loadBrightness]; [self _registerSignalHandling]; + + // If the process is trusted, register for keyboard events; otherwise wait for the user to declare the process trusted + if (AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)@{(__bridge NSString *)kAXTrustedCheckOptionPrompt: @true})) { + [self _registerGlobalKeyboardEvents]; + } + else { + [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(startMonitoringKeysIfProcessTrusted:) userInfo:nil repeats:YES]; + } +} + +- (void) startMonitoringKeysIfProcessTrusted:(NSTimer*)timer +{ + // Check if the process is trusted without prompting the user again + if (AXIsProcessTrustedWithOptions(nil)) { + [self _registerGlobalKeyboardEvents]; + [timer invalidate]; + } } void shutdownSignalHandler(int signal) @@ -184,13 +254,7 @@ - (void)_registerSignalHandling - (void)applicationWillTerminate:(NSNotification *)aNotification { - [self _willTerminate]; -} -- (void)_willTerminate -{ - NSLog(@"willTerminate"); - [self _saveBrightness]; } - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sender @@ -198,46 +262,121 @@ - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sende return NO; } -- (void)setBrightness:(float)value +- (void)incrementScreenBrightnessWithStep:(int)deltaInSubsteps inAllScreens:(BOOL)updateAllScreens { - _brightness = value; - - CGDirectDisplayID display = CGSMainDisplayID(); - - if (_BSDoGraphicWithMeterAndTimeout != NULL) - { - // El Capitan and probably older systems - _BSDoGraphicWithMeterAndTimeout(display, BSGraphicBacklightMeter, 0x0, value/100.f, 1); + if (! updateAllScreens) { + // Set the brightness only in the main screen + [self incrementScreenBrightnessWithStep:deltaInSubsteps inScreen:NSScreen.mainScreen]; } else { - // Sierra+ - [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:CGSMainDisplayID() priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:value/brightnessStep totalChiclets:100.f/brightnessStep locked:NO]; - } - - for (NSScreen *screen in NSScreen.screens) { - NSDictionary *description = [screen deviceDescription]; - if ([description objectForKey:@"NSDeviceIsScreen"]) { - CGDirectDisplayID screenNumber = [[description objectForKey:@"NSScreenNumber"] unsignedIntValue]; - - set_control(screenNumber, BRIGHTNESS, value); + for (NSScreen* screen in NSScreen.screens) { + [self incrementScreenBrightnessWithStep:deltaInSubsteps inScreen:screen]; } } } -- (float)brightness -{ - return _brightness; -} - -- (void)increaseBrightness +- (void) incrementScreenBrightnessWithStep:(int)deltaInSubsteps inScreen:(NSScreen*)targetScreen { - self.brightness = MIN(self.brightness+brightnessStep,100); -} + CGDirectDisplayID currentDisplayId = [targetScreen.deviceDescription [@"NSScreenNumber"] unsignedIntValue]; + + BOOL isBuiltinDisplay = CGDisplayIsBuiltin(currentDisplayId); + io_service_t builtinDisplayIOService = isBuiltinDisplay ? CGDisplayIOServicePort(currentDisplayId) : IO_OBJECT_NULL; + + BOOL isCurrentBrighnessAvailableFromExternalDisplay = NO; + uint maxExternalDisplayBrightness = 100; + NSString* currentExternalDisplayDefaultsKey; + + // Get the current brightness + int currentBrightnessInSubsteps = -1; + + if (! isBuiltinDisplay) { + + uint currentBrightness = 50; + + // Get the current display brightness + // Fist, try user defaults to avoid waiting for a timeout if the display is known not to support DDCRead; + // If user defaults are not set, read the brightness value from the display + + BOOL isCurrentBrighnessReadFromDefaults = NO; + currentExternalDisplayDefaultsKey = [NSString stringWithFormat:@"%u", currentDisplayId]; + NSDictionary* savedDisplayBrighnesses = [NSUserDefaults.standardUserDefaults objectForKey:kDisplaysBrightnessDefaultsKey]; + if ([savedDisplayBrighnesses isKindOfClass:[NSDictionary class]]) { + NSNumber* savedCurrentBrightness = savedDisplayBrighnesses [currentExternalDisplayDefaultsKey]; + if ([savedCurrentBrightness isKindOfClass:[NSNumber class]]) { + currentBrightness = (uint) savedCurrentBrightness.unsignedIntegerValue; + isCurrentBrighnessReadFromDefaults = YES; + } + } + + if (! isCurrentBrighnessReadFromDefaults) { + isCurrentBrighnessAvailableFromExternalDisplay = get_control(currentDisplayId, BRIGHTNESS, ¤tBrightness, &maxExternalDisplayBrightness); + } -- (void)decreaseBrightness -{ - self.brightness = MAX(self.brightness-brightnessStep,0); + currentBrightnessInSubsteps = round((double)currentBrightness / (double)maxExternalDisplayBrightness * (double)brightnessSubstepsCount); + } + else { + float currentBrightness; + IOReturn ioResult = IODisplayGetFloatParameter(builtinDisplayIOService, kNilOptions, CFSTR(kIODisplayBrightnessKey), ¤tBrightness); + if (ioResult == kIOReturnSuccess) { + // currentBrightness is in the [0, 1] range: convert it to substeps + currentBrightnessInSubsteps = round((double)currentBrightness * (double)brightnessSubstepsCount); + } + } + + if (currentBrightnessInSubsteps != -1) { + + // Compute the new brightness for this display + int newBrightnessInSubsteps = MIN(MAX(0, currentBrightnessInSubsteps + deltaInSubsteps), brightnessSubstepsCount); + if (abs(deltaInSubsteps) != 1) { + // newBrightnessInSubsteps must be a multiple of deltaInSubsteps + newBrightnessInSubsteps = (newBrightnessInSubsteps / deltaInSubsteps) * deltaInSubsteps; + } + + if (newBrightnessInSubsteps != currentBrightnessInSubsteps) { + // Set the new brightness + if (! isBuiltinDisplay) { + uint newBrightness = (uint) round((double)newBrightnessInSubsteps / (double)brightnessSubstepsCount * (double)maxExternalDisplayBrightness); + + if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { + + // NSLog(@"New brightness: %d", newBrightness); + + if (! isCurrentBrighnessAvailableFromExternalDisplay) { + // Save the new brighness value + NSMutableDictionary* newDisplayBrighnesses; + NSDictionary* savedDisplayBrighnesses = [NSUserDefaults.standardUserDefaults objectForKey:kDisplaysBrightnessDefaultsKey]; + + if ([savedDisplayBrighnesses isKindOfClass:[NSDictionary class]]) { + newDisplayBrighnesses = [NSMutableDictionary dictionaryWithDictionary:savedDisplayBrighnesses]; + } + else { + newDisplayBrighnesses = [NSMutableDictionary new]; + } + + newDisplayBrighnesses [currentExternalDisplayDefaultsKey] = @(newBrightness); + + [NSUserDefaults.standardUserDefaults setObject:newDisplayBrighnesses forKey:kDisplaysBrightnessDefaultsKey]; + } + } + else { + // Brightness has not been set + newBrightnessInSubsteps = currentBrightnessInSubsteps; + } + } + else { + // Builtin display + float newBrightness = (double)newBrightnessInSubsteps / (double)brightnessSubstepsCount; + IOReturn ioResult = IODisplaySetFloatParameter(builtinDisplayIOService, kNilOptions, CFSTR(kIODisplayBrightnessKey), newBrightness); + if (ioResult != kIOReturnSuccess) { + // Brightness has not been set + newBrightnessInSubsteps = currentBrightnessInSubsteps; + } + } + } + + // Display the brighness level OSD + showBrightnessLevelPaneOnDisplay(newBrightnessInSubsteps, currentDisplayId); + } } - @end diff --git a/NativeDisplayBrightness/BezelServices.h b/NativeDisplayBrightness/BezelServices.h index 118ad26..2aac3be 100644 --- a/NativeDisplayBrightness/BezelServices.h +++ b/NativeDisplayBrightness/BezelServices.h @@ -9,8 +9,6 @@ #ifndef BezelServices_h #define BezelServices_h -#include "CoreGraphicsPriv.h" - typedef enum { BSGraphicBacklightMeter = 0xfffffff7, BSGraphicBacklightFailure = 0xfffffff6, diff --git a/NativeDisplayBrightness/CoreGraphicsPriv.h b/NativeDisplayBrightness/CoreGraphicsPriv.h deleted file mode 100644 index 37284fe..0000000 --- a/NativeDisplayBrightness/CoreGraphicsPriv.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// CoreGraphicsPriv.h -// NativeDisplayBrightness -// -// Created by Benno Krauss on 23.10.16. -// Copyright © 2016 Benno Krauss. All rights reserved. -// - -#ifndef CoreGraphicsPriv_h -#define CoreGraphicsPriv_h - -CG_EXTERN CGDirectDisplayID CGSMainDisplayID(void); - -#endif /* CoreGraphicsPriv_h */ diff --git a/NativeDisplayBrightness/DDC.c b/NativeDisplayBrightness/DDC.c new file mode 100644 index 0000000..ae0f634 --- /dev/null +++ b/NativeDisplayBrightness/DDC.c @@ -0,0 +1,414 @@ +// +// 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" + +#define kMaxRequests 10 + +/* + + 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 + */ +static io_service_t IOFramebufferPortFromCGDisplayID(CGDirectDisplayID displayID) +{ + io_iterator_t iter; + io_service_t serv, servicePort = 0; + + kern_return_t err = IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(IOFRAMEBUFFER_CONFORMSTO), &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 = 0, productID = 0, serialNumber = 0; + CFNumberRef vendorIDRef, productIDRef, serialNumberRef; +#ifdef DEBUG + CFStringRef location = 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)); + 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); + + if (CFDictionaryGetValueIfPresent(info, CFSTR(kDisplayProductID), (const void**)&productIDRef)) + success &= CFNumberGetValue(productIDRef, kCFNumberCFIndexType, &productID); + + IOItemCount busCount; + IOFBGetI2CInterfaceCount(serv, &busCount); + + if (!success || busCount < 1 || CGDisplayIsBuiltin(displayID)) { + // this does not seem to be a DDC-enabled display, skip it + CFRelease(info); + continue; + } + // if (framebuffer.hasDDCConnect(0)) // https://developer.apple.com/reference/kernel/ioframebuffer/1813510-hasddcconnect?language=objc + // 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; + } + +#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); + printf(" UN:%d", CGDisplayUnitNumber(displayID)); + printf(" IN:%d", iter); + printf(" Serial:%s\n\n", CFStringGetCStringPtr(serial, kCFStringEncodingUTF8)); +#endif + servicePort = serv; + CFRelease(info); + break; + } + + IOObjectRelease(iter); + return servicePort; +} + +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); + } + if (request->replyTransactionType == kIOI2CNoTransactionType) + usleep(20000); + dispatch_semaphore_signal(queue); + return result && request->result == KERN_SUCCESS; +} + +bool DDCWrite(CGDirectDisplayID displayID, struct DDCWriteCommand *write) { + IOI2CRequest request; + UInt8 data[128]; + + bzero( &request, sizeof(request)); + + request.commFlags = 0; + + 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] = 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]; + + request.replyTransactionType = kIOI2CNoTransactionType; + request.replyBytes = 0; + + bool result = DisplayRequest(displayID, &request); + return result; +} + +bool DDCRead(CGDirectDisplayID displayID, struct DDCReadCommand *read) { + IOI2CRequest request; + UInt8 reply_data[11] = {}; + bool result = false; + UInt8 data[128]; + + for (int i=1; i<=kMaxRequests; i++) { + bzero(&request, sizeof(request)); + + request.commFlags = 0; + request.sendAddress = 0x6E; + request.sendTransactionType = kIOI2CSimpleTransactionType; + request.sendBuffer = (vm_address_t) &data[0]; + request.sendBytes = 5; + request.minReplyDelay = 10; // too short can freeze kernel + + data[0] = 0x51; + data[1] = 0x82; + data[2] = 0x01; + data[3] = read->control_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->success = false; + 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 + } + read->success = true; + read->max_value = reply_data[7]; + read->current_value = reply_data[9]; + return result; +} + +UInt32 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 + */ + + kern_return_t kr; + io_iterator_t io_objects; + io_service_t io_service; + + kr = IOServiceGetMatchingServices(kIOMasterPortDefault, + IOServiceNameMatching("IOFramebufferI2CInterface"), &io_objects); + + if (kr != KERN_SUCCESS) { + printf("E: Fatal - No matching service! \n"); + return 0; + } + + UInt32 supportedType = 0; + + 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; + // http://hackipedia.org/Hardware/video/connectors/DisplayPort/VESA%20DisplayPort%20Standard%20v1.1a.pdf + // http://www.electronic-products-design.com/geek-area/displays/display-port + } +#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 = {}; +/*! from https://opensource.apple.com/source/IOGraphics/IOGraphics-513.1/IOGraphicsFamily/IOKit/i2c/IOI2CInterface.h.auto.html + * not in https://developer.apple.com/reference/kernel/1659924-ioi2cinterface.h/ioi2crequest?changes=latest_beta&language=objc + * @struct IOI2CRequest + * @abstract A structure defining an I2C bus transaction. + * @discussion This structure is used to request an I2C transaction consisting of a send (write) to and reply (read) from a device, either of which is optional, to be carried out atomically on an I2C bus. + * @field __reservedA Set to zero. + * @field result The result of the transaction. Common errors are kIOReturnNoDevice if there is no device responding at the given address, kIOReturnUnsupportedMode if the type of transaction is unsupported on the requested bus. + * @field completion A completion routine to be executed when the request completes. If NULL is passed, the request is synchronous, otherwise it may execute asynchronously. + * @field commFlags Flags that modify the I2C transaction type. The following flags are defined:
+ * kIOI2CUseSubAddressCommFlag Transaction includes a subaddress.
+ * @field minReplyDelay Minimum delay as absolute time between send and reply transactions. + * @field sendAddress I2C address to write. + * @field sendSubAddress I2C subaddress to write. + * @field __reservedB Set to zero. + * @field sendTransactionType The following types of transaction are defined for the send part of the request:
+ * kIOI2CNoTransactionType No send transaction to perform.
+ * kIOI2CSimpleTransactionType Simple I2C message.
+ * kIOI2CCombinedTransactionType Combined format I2C R/~W transaction.
+ * @field sendBuffer Pointer to the send buffer. + * @field sendBytes Number of bytes to send. Set to actual bytes sent on completion of the request. + * @field replyAddress I2C Address from which to read. + * @field replySubAddress I2C Address from which to read. + * @field __reservedC Set to zero. + * @field replyTransactionType The following types of transaction are defined for the reply part of the request:
+ * kIOI2CNoTransactionType No reply transaction to perform.
+ * kIOI2CSimpleTransactionType Simple I2C message.
+ * kIOI2CDDCciReplyTransactionType DDC/ci message (with embedded length). See VESA DDC/ci specification.
+ * kIOI2CCombinedTransactionType Combined format I2C R/~W transaction.
+ * @field replyBuffer Pointer to the reply buffer. + * @field replyBytes Max bytes to reply (size of replyBuffer). Set to actual bytes received on completion of the request. + * @field __reservedD Set to zero. + */ + + 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; + sum = 0; + } + sum += data[i++]; + } + return !sum; +} diff --git a/NativeDisplayBrightness/DDC.h b/NativeDisplayBrightness/DDC.h index e693a43..d2ef94d 100644 --- a/NativeDisplayBrightness/DDC.h +++ b/NativeDisplayBrightness/DDC.h @@ -19,6 +19,7 @@ #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 @@ -48,12 +49,12 @@ #define ORIENTATION 0xAA #define AUDIO_MUTE 0x8D #define SETTINGS 0xB0 //unsure on this one -#define ON_SCREEN_DISPLAY 0xCA +#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 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 @@ -70,6 +71,7 @@ struct DDCWriteCommand struct DDCReadCommand { UInt8 control_id; + bool success; UInt8 max_value; UInt8 current_value; }; @@ -84,12 +86,14 @@ struct EDID { UInt8 year : 8; UInt8 versionmajor : 8; UInt8 versionminor : 8; - UInt8 digitalinput : 1; + 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; @@ -97,6 +101,7 @@ struct EDID { UInt8 green : 1; UInt8 serrated : 1; } analog; + } videoinput; UInt8 maxh : 8; UInt8 maxv : 8; UInt8 gamma : 8; @@ -181,14 +186,8 @@ struct EDID { UInt8 interlaced : 1; UInt8 stereo : 2; UInt8 synctype : 2; - 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 { @@ -230,10 +229,7 @@ struct EDID { UInt8 gamma2 : 8; UInt32 : 24; } whitepoint; - } descriptor1; - union descriptor descriptor2; - union descriptor descriptor3; - union descriptor descriptor4; + } descriptors[4]; UInt8 extensions : 8; UInt8 checksum : 8; }; @@ -241,5 +237,5 @@ struct EDID { bool DDCWrite(CGDirectDisplayID displayID, struct DDCWriteCommand *write); bool DDCRead(CGDirectDisplayID displayID, struct DDCReadCommand *read); bool EDIDTest(CGDirectDisplayID displayID, struct EDID *edid); - +UInt32 SupportedTransactionType(); #endif diff --git a/NativeDisplayBrightness/DDC.m b/NativeDisplayBrightness/DDC.m deleted file mode 100644 index 1408db3..0000000 --- a/NativeDisplayBrightness/DDC.m +++ /dev/null @@ -1,249 +0,0 @@ -// -// 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" -#define kDelayBase 100 - -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 = 0, productID = 0, 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; -} - -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; - CFNumberRef flags = NULL; - CFIndex flag; - if (request->minReplyDelay - && (flags = IORegistryEntryCreateCFProperty(interface, CFSTR(kIOI2CSupportedCommFlagsKey), kCFAllocatorDefault, 0)) - && CFNumberGetValue(flags, kCFNumberCFIndexType, &flag) - && flag == kIOI2CUseSubAddressCommFlag) - request->minReplyDelay *= kMillisecondScale; - if (flags) - CFRelease(flags); - IOI2CConnectRef connect; - if (IOI2CInterfaceOpen(interface, kNilOptions, &connect) == KERN_SUCCESS) { - result = (IOI2CSendRequest(connect, kNilOptions, request) == KERN_SUCCESS); - IOI2CInterfaceClose(connect, kNilOptions); - } - IOObjectRelease(interface); - if (result) break; - } - } - } - if (request->replyTransactionType == kIOI2CNoTransactionType) - usleep(kDelayBase * kMicrosecondScale); - dispatch_semaphore_signal(queue); - return result && request->result == KERN_SUCCESS; -} - -bool DDCWrite(CGDirectDisplayID displayID, struct DDCWriteCommand *write) { - IOI2CRequest request; - UInt8 data[128]; - - bzero( &request, sizeof(request)); - - request.commFlags = 0; - - 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] = 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]; - - - request.replyTransactionType = kIOI2CNoTransactionType; - request.replyBytes = 0; - - - bool result = DisplayRequest(displayID, &request); - return result; -} - -bool DDCRead(CGDirectDisplayID displayID, struct DDCReadCommand *read) { - IOI2CRequest request; - UInt8 reply_data[11] = {}; - bool result = false; - UInt8 data[128]; - - - bzero( &request, sizeof(request)); - - request.commFlags = 0; - - request.sendAddress = 0x6E; - request.sendTransactionType = kIOI2CSimpleTransactionType; - request.sendBuffer = (vm_address_t) &data[0]; - request.sendBytes = 5; - request.minReplyDelay = kDelayBase; - - data[0] = 0x51; - data[1] = 0x82; - data[2] = 0x01; - data[3] = read->control_id; - data[4] = 0x6E ^ data[0] ^ data[1] ^ data[2] ^ data[3]; - - request.replyTransactionType = kIOI2CDDCciReplyTransactionType; - 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])); - read->max_value = reply_data[7]; - read->current_value = reply_data[9]; - return result; -} - -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; - sum = 0; - } - sum += data[i++]; - } - return !sum; -} diff --git a/NativeDisplayBrightness/Info.plist b/NativeDisplayBrightness/Info.plist index 2dab834..435b981 100644 --- a/NativeDisplayBrightness/Info.plist +++ b/NativeDisplayBrightness/Info.plist @@ -2,8 +2,6 @@ - LSBackgroundOnly - CFBundleDevelopmentRegion en CFBundleExecutable @@ -19,13 +17,15 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 0.0.8 CFBundleVersion - 1 + 21 + LSBackgroundOnly + LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright - Copyright © 2016 Benno Krauss. All rights reserved. + Copyright © 2017 JL Jumpertz + Benno Krauss. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass diff --git a/NativeDisplayBrightness/OSD.h b/NativeDisplayBrightness/OSD.h index 64f8ae6..7ecc198 100644 --- a/NativeDisplayBrightness/OSD.h +++ b/NativeDisplayBrightness/OSD.h @@ -9,8 +9,6 @@ #ifndef OSD_h #define OSD_h -#include "CoreGraphicsPriv.h" - typedef enum { OSDGraphicBacklight = 1,//0xfffffff7, OSDGraphicEject = 6, diff --git a/Readme.md b/Readme.md index 05c461b..f9993d3 100644 --- a/Readme.md +++ b/Readme.md @@ -1,14 +1,35 @@ # NativeDisplayBrightness -*Control your desktop monitor brightness just like on a MacBook!* +*Control the brightness of external monitors with your Mac's brightness keys!* -![native brightness UI](https://raw.githubusercontent.com/Bensge/NativeDisplayBrightness/master/nativeUI.png) +![native brightness UI](nativeUI.png) -This a utility application to control monitor brightness with the F1, F2 keys. It utilizes DDC/CI, but this app doesn't have the freezing issues that similar aplications tend to suffer from. +This a utility application to control the brightness of external monitors directly from your keyboard. -This app also shows the **native** system UI when changing brightness! It uses the private `BezelServices` framework for this. +Use the `F1` / `F2` key to decrease / increase the brightness of the screen showing the active window. If you have an Apple or similar keyboard, you probably need to also press the `fn` key. -Needless to say, your monitor needs to support DDC/CI for this app to work. +For a finer brightness level adjustment, you add the option key, i.e use `alt` + `F1` to decrease the brightness, or `alt` + `F2` to increase it. + +This app shows the **native** system UI when changing brightness! + +## Multiple monitors support + +If you have multiple external monitors connected to your Mac, the brightness adjustment is done on the monitor with the currently active window, and the brightness system UI is displayed on the adjusted monitor. + +If you press the `shift` key in conjunction with the `F1` or `F2` keys, then the brightness is ajusted on all connected screens simultaneously (including the builtin screen on a MacBook) and the brightness system UI is displayed on every screen to indicate the current brightness of the screen. +## Monitors compatibility + +Your monitor needs to support DDC/CI for this app to work. If you don't see the brightness system UI displayed on your monitor when pressing the F1 / F2 keys, this means that your monitor is not supported. + +If your monitor supports reading the current brightness value from DDC/CI, the app increments / decrements the brightness starting from the monitor current brightness value. This allows you to set the brightness using the monitor's OSD and to adjust it later with the app + +## Implementation notes + +For showing the native system UI for brightness adjustment, this app uses the macOS private framework `BezelServices`. + +## Requirements + +macOS version: 10.10 to 10.14 ## License