From 7c8ac56ede45e4af08e276bd171243732c5a7fff Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 10:44:27 +0200 Subject: [PATCH 1/8] Support for multiple monitors. - Manage the brightness individually for each connected external monitor. - Read the brightness from the target monitor if available; otherwise maintain a @[display_id: brightness] dictionary in the user defaults. - Display the Brightness system OSD on the target monitor. - Upgraded DDC to a more-recent version. - Removed useless CoreGraphicsPriv.h. --- .../project.pbxproj | 14 +- NativeDisplayBrightness/AppDelegate.m | 196 ++++++--- NativeDisplayBrightness/BezelServices.h | 2 - NativeDisplayBrightness/CoreGraphicsPriv.h | 14 - NativeDisplayBrightness/DDC.c | 414 ++++++++++++++++++ NativeDisplayBrightness/DDC.h | 30 +- NativeDisplayBrightness/DDC.m | 249 ----------- NativeDisplayBrightness/OSD.h | 2 - Readme.md | 10 +- 9 files changed, 571 insertions(+), 360 deletions(-) delete mode 100644 NativeDisplayBrightness/CoreGraphicsPriv.h create mode 100644 NativeDisplayBrightness/DDC.c delete mode 100644 NativeDisplayBrightness/DDC.m diff --git a/NativeDisplayBrightness.xcodeproj/project.pbxproj b/NativeDisplayBrightness.xcodeproj/project.pbxproj index ba50f8d..4dd9a05 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 = ""; @@ -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 */, ); diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index 34d7721..6cafc0e 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -11,11 +11,50 @@ #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 NSString *const kDisplaysBrightnessDefaultsKey = @"displays-brightness"; static const float brightnessStep = 100/16.f; #pragma mark - variables @@ -24,17 +63,40 @@ #pragma mark - functions -void set_control(CGDirectDisplayID cdisplay, uint control_id, uint new_value) +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; } +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, CGEventType type, @@ -58,12 +120,10 @@ CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, @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 { @@ -107,7 +167,7 @@ - (void)_registerGlobalKeyboardEvents if (event.type == NSEventTypeKeyDown) { dispatch_async(dispatch_get_main_queue(), ^{ - [self decreaseBrightness]; + [self incrementMainScreenBrightness: -brightnessStep]; }); } } @@ -116,7 +176,7 @@ - (void)_registerGlobalKeyboardEvents if (event.type == NSEventTypeKeyDown) { dispatch_async(dispatch_get_main_queue(), ^{ - [self increaseBrightness]; + [self incrementMainScreenBrightness: brightnessStep]; }); } } @@ -135,21 +195,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]) @@ -159,7 +204,6 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification [self _configureLoginItem]; [self _checkTrusted]; [self _registerGlobalKeyboardEvents]; - [self _loadBrightness]; [self _registerSignalHandling]; } @@ -184,13 +228,7 @@ - (void)_registerSignalHandling - (void)applicationWillTerminate:(NSNotification *)aNotification { - [self _willTerminate]; -} -- (void)_willTerminate -{ - NSLog(@"willTerminate"); - [self _saveBrightness]; } - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sender @@ -198,46 +236,70 @@ - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sende return NO; } -- (void)setBrightness:(float)value +- (void)incrementMainScreenBrightness:(int)delta { - _brightness = value; + CGDirectDisplayID currentDisplayId = [NSScreen.mainScreen.deviceDescription [@"NSScreenNumber"] unsignedIntValue]; - CGDirectDisplayID display = CGSMainDisplayID(); - - if (_BSDoGraphicWithMeterAndTimeout != NULL) - { - // El Capitan and probably older systems - _BSDoGraphicWithMeterAndTimeout(display, BSGraphicBacklightMeter, 0x0, value/100.f, 1); - } - 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); + if (! CGDisplayIsBuiltin(currentDisplayId)) { + + uint currentBrightness = 50; + uint maxBrightness = 100; + + // 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; + NSString* currentDisplayIdKey = [NSString stringWithFormat:@"%u", currentDisplayId]; + NSDictionary* savedDisplayBrighnesses = [NSUserDefaults.standardUserDefaults objectForKey:kDisplaysBrightnessDefaultsKey]; + if ([savedDisplayBrighnesses isKindOfClass:[NSDictionary class]]) { + NSNumber* savedCurrentBrightness = savedDisplayBrighnesses [currentDisplayIdKey]; + if ([savedCurrentBrightness isKindOfClass:[NSNumber class]]) { + currentBrightness = (uint) savedCurrentBrightness.unsignedIntegerValue; + isCurrentBrighnessReadFromDefaults = YES; + } + } + + BOOL isCurrentBrighnessAvailableFromDisplay = NO; + if (! isCurrentBrighnessReadFromDefaults) { + isCurrentBrighnessAvailableFromDisplay = get_control(currentDisplayId, BRIGHTNESS, ¤tBrightness, &maxBrightness); } - } -} - -- (float)brightness -{ - return _brightness; -} - -- (void)increaseBrightness -{ - self.brightness = MIN(self.brightness+brightnessStep,100); -} -- (void)decreaseBrightness -{ - self.brightness = MAX(self.brightness-brightnessStep,0); + uint newBrightness = MIN((uint)MAX((int)currentBrightness + delta, 0), maxBrightness); + + if (newBrightness != currentBrightness) { + + if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { + + // Display the brighness level OSD + if (_BSDoGraphicWithMeterAndTimeout != NULL) + { + // El Capitan and probably older systems + _BSDoGraphicWithMeterAndTimeout(currentDisplayId, BSGraphicBacklightMeter, 0x0, (float)newBrightness/100.f, 1); + } + else { + // Sierra+ + [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:currentDisplayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)newBrightness/brightnessStep totalChiclets:100.f/brightnessStep locked:NO]; + } + + if (! isCurrentBrighnessAvailableFromDisplay) { + // 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 [currentDisplayIdKey] = @(newBrightness); + + [NSUserDefaults.standardUserDefaults setObject:newDisplayBrighnesses forKey:kDisplaysBrightnessDefaultsKey]; + } + } + } + } } - @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/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..3fc1bca 100644 --- a/Readme.md +++ b/Readme.md @@ -8,7 +8,15 @@ This a utility application to control monitor brightness with the F1, F2 keys. I This app also shows the **native** system UI when changing brightness! It uses the private `BezelServices` framework for this. -Needless to say, your monitor needs to support DDC/CI for this app to work. +## 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 brigness staring from the monitor current brightness value. This allows you to set the brighness using the monitor's OSD and to adjust it later with the app + +## Multiple monitors support + +If you have multiple external monitors connected to your Mac, the brighness adjustment is done on the monitor with the currently active window, and the brightness system UI is displayed on the adjusted monitor. ## License From e6a7389dee96dd6d02ef678c12dc8a8d324f90e3 Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 10:48:16 +0200 Subject: [PATCH 2/8] Fixed typo in readme --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 3fc1bca..274fd99 100644 --- a/Readme.md +++ b/Readme.md @@ -12,7 +12,7 @@ This app also shows the **native** system UI when changing brightness! It uses t 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 brigness staring from the monitor current brightness value. This allows you to set the brighness using the monitor's OSD and to adjust it later with the app +If your monitor supports reading the current brightness value from DDC/CI, the app increments / decrements the brigness starting from the monitor current brightness value. This allows you to set the brighness using the monitor's OSD and to adjust it later with the app ## Multiple monitors support From a2c80ac19f51845fb8dae2d95933916df2ca0815 Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 11:24:01 +0200 Subject: [PATCH 3/8] Better first-launch experience. If the app is not trusted in the Accessibility preference panel when started, it now waits that the user trusts it before installing the key event monitor. Previously the app had to be relaunched after being authorized to grab the keys, which was not user-friendly at all, because that the app is invisible and had to be killed via the Activity Monitor!! --- NativeDisplayBrightness/AppDelegate.m | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index 6cafc0e..4540fde 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -152,12 +152,6 @@ - (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) { @@ -202,9 +196,22 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification [self _loadOSDFramework]; } [self _configureLoginItem]; - [self _checkTrusted]; - [self _registerGlobalKeyboardEvents]; [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 repeats:YES block:^(NSTimer * _Nonnull timer) { + + // Check if the process is trusted without prompting the user again + if (AXIsProcessTrustedWithOptions(nil)) { + [self _registerGlobalKeyboardEvents]; + [timer invalidate]; + } + }]; + } } void shutdownSignalHandler(int signal) From e8ff613e81c5a48a0d2ad1798ba80758f8846e5f Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 12:15:00 +0200 Subject: [PATCH 4/8] Fixed typo in readme --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 274fd99..bccf217 100644 --- a/Readme.md +++ b/Readme.md @@ -12,11 +12,11 @@ This app also shows the **native** system UI when changing brightness! It uses t 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 brigness starting from the monitor current brightness value. This allows you to set the brighness using the monitor's OSD and to adjust it later with the app +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 ## Multiple monitors support -If you have multiple external monitors connected to your Mac, the brighness adjustment is done on the monitor with the currently active window, and the brightness system UI is displayed on the adjusted monitor. +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. ## License From 737ae3d420f2ff6e075997f0f7f2587e64f4a564 Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 15:49:23 +0200 Subject: [PATCH 5/8] Fix first launch issue on macOS 10.11 El Capitan and before. - Waiting for accessibility authorization is now done using the selector-based NSTimer API, making it work on macOS versions < 10.12. - Updated version information and copyright. --- .../project.pbxproj | 2 ++ NativeDisplayBrightness/AppDelegate.m | 18 ++++++++++-------- NativeDisplayBrightness/Info.plist | 10 +++++----- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/NativeDisplayBrightness.xcodeproj/project.pbxproj b/NativeDisplayBrightness.xcodeproj/project.pbxproj index 4dd9a05..1f6aa7e 100644 --- a/NativeDisplayBrightness.xcodeproj/project.pbxproj +++ b/NativeDisplayBrightness.xcodeproj/project.pbxproj @@ -268,6 +268,7 @@ COMBINE_HIDPI_IMAGES = YES; 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)"; }; @@ -280,6 +281,7 @@ COMBINE_HIDPI_IMAGES = YES; 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)"; }; diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index 4540fde..87207af 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -203,14 +203,16 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification [self _registerGlobalKeyboardEvents]; } else { - [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:YES block:^(NSTimer * _Nonnull timer) { - - // Check if the process is trusted without prompting the user again - if (AXIsProcessTrustedWithOptions(nil)) { - [self _registerGlobalKeyboardEvents]; - [timer invalidate]; - } - }]; + [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]; } } diff --git a/NativeDisplayBrightness/Info.plist b/NativeDisplayBrightness/Info.plist index 2dab834..82ea897 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.6 CFBundleVersion - 1 + 18 + 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 From 47c8a4355416920208f45ee7d548b5dd6fd390c2 Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Aug 2017 22:57:07 +0200 Subject: [PATCH 6/8] Show the brightness pane when at min or max level. - Show the brightness level pane on the target display if the brightness level is not actually changed because already at the min or max level. - [Internal] Declare C functions and variable as static in AppDelegate.m to better express their expected scope. --- NativeDisplayBrightness/AppDelegate.m | 37 +++++++++++++++++---------- NativeDisplayBrightness/Info.plist | 2 +- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index 87207af..fac79d9 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -59,11 +59,11 @@ #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 -BOOL set_control(CGDirectDisplayID display_id, 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; @@ -78,7 +78,7 @@ BOOL set_control(CGDirectDisplayID display_id, uint control_id, uint new_value) return isCommandOk; } -BOOL get_control(CGDirectDisplayID display_id, uint control_id, uint* current_value, uint* max_value) +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); @@ -98,7 +98,7 @@ BOOL get_control(CGDirectDisplayID display_id, uint control_id, uint* current_va return isCommandOk; } -CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, +static CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) @@ -115,6 +115,21 @@ CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, return event; } +static void showBrightnessLevelPaneOnDisplay (uint brightnessLevel, CGDirectDisplayID displayId) +{ + if (_BSDoGraphicWithMeterAndTimeout != NULL) + { + // El Capitan and probably older systems + _BSDoGraphicWithMeterAndTimeout(displayId, BSGraphicBacklightMeter, 0x0, (float)brightnessLevel/100.f, 1); + } + else { + // Sierra+ + [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:displayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)brightnessLevel/brightnessStep totalChiclets:100.f/brightnessStep locked:NO]; + } + +} + + #pragma mark - AppDelegate @interface AppDelegate () @@ -281,15 +296,7 @@ - (void)incrementMainScreenBrightness:(int)delta if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { // Display the brighness level OSD - if (_BSDoGraphicWithMeterAndTimeout != NULL) - { - // El Capitan and probably older systems - _BSDoGraphicWithMeterAndTimeout(currentDisplayId, BSGraphicBacklightMeter, 0x0, (float)newBrightness/100.f, 1); - } - else { - // Sierra+ - [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:currentDisplayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)newBrightness/brightnessStep totalChiclets:100.f/brightnessStep locked:NO]; - } + showBrightnessLevelPaneOnDisplay(newBrightness, currentDisplayId); if (! isCurrentBrighnessAvailableFromDisplay) { // Save the new brighness value @@ -308,6 +315,10 @@ - (void)incrementMainScreenBrightness:(int)delta } } } + else { + // Min or max brightness level: present the OSD to provide a feedback to the user, but don't send a command + showBrightnessLevelPaneOnDisplay(newBrightness, currentDisplayId); + } } } diff --git a/NativeDisplayBrightness/Info.plist b/NativeDisplayBrightness/Info.plist index 82ea897..607ce92 100644 --- a/NativeDisplayBrightness/Info.plist +++ b/NativeDisplayBrightness/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString 0.0.6 CFBundleVersion - 18 + 19 LSBackgroundOnly LSMinimumSystemVersion From cd6191cf10677f160cc00fb63cdde8259e2659bd Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Sun, 3 Sep 2017 23:27:15 +0200 Subject: [PATCH 7/8] Allow fine brightness adjustment when the option key is pressed. - Rewritten management of the brightness level, so that possible brightness levels are evenly distributed in the monitor's possible range. - Added the support of quarter-step adjustments when the Option modifier key is pressed (similar to the system brightness management on the internal display when Shift+Option is pressed). --- NativeDisplayBrightness/AppDelegate.m | 53 ++++++++++++++++----------- NativeDisplayBrightness/Info.plist | 4 +- Readme.md | 12 +++++- 3 files changed, 44 insertions(+), 25 deletions(-) diff --git a/NativeDisplayBrightness/AppDelegate.m b/NativeDisplayBrightness/AppDelegate.m index fac79d9..5696b91 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -55,7 +55,9 @@ #pragma mark - constants static NSString *const kDisplaysBrightnessDefaultsKey = @"displays-brightness"; -static const float brightnessStep = 100/16.f; +static const int brightnessStepsCount = 16; +static const int brightnessSubstepsPerStep = 4; +static const int brightnessSubstepsCount = brightnessStepsCount * brightnessSubstepsPerStep; #pragma mark - variables @@ -115,16 +117,16 @@ static CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, return event; } -static void showBrightnessLevelPaneOnDisplay (uint brightnessLevel, CGDirectDisplayID displayId) +static void showBrightnessLevelPaneOnDisplay (uint brightnessLevelInSubsteps, CGDirectDisplayID displayId) { if (_BSDoGraphicWithMeterAndTimeout != NULL) { // El Capitan and probably older systems - _BSDoGraphicWithMeterAndTimeout(displayId, BSGraphicBacklightMeter, 0x0, (float)brightnessLevel/100.f, 1); + _BSDoGraphicWithMeterAndTimeout(displayId, BSGraphicBacklightMeter, 0x0, (float)brightnessLevelInSubsteps/(float)brightnessSubstepsCount, 1); } else { // Sierra+ - [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:displayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)brightnessLevel/brightnessStep totalChiclets:100.f/brightnessStep locked:NO]; + [[NSClassFromString(@"OSDManager") sharedManager] showImage:OSDGraphicBacklight onDisplayID:displayId priority:OSDPriorityDefault msecUntilFade:1000 filledChiclets:(float)brightnessLevelInSubsteps totalChiclets:brightnessSubstepsCount locked:NO]; } } @@ -169,23 +171,22 @@ - (void)_configureLoginItem - (void)_registerGlobalKeyboardEvents { - [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown | NSEventMaskKeyUp handler:^(NSEvent *_Nonnull event) { - //NSLog(@"event!!"); - if (event.keyCode == kVK_F1) + [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent *_Nonnull event) { + if (event.type == NSEventTypeKeyDown) { - if (event.type == NSEventTypeKeyDown) - { - dispatch_async(dispatch_get_main_queue(), ^{ - [self incrementMainScreenBrightness: -brightnessStep]; - }); - } - } - else if (event.keyCode == kVK_F2) - { - if (event.type == NSEventTypeKeyDown) + BOOL isOptionModifierPressed = (event.modifierFlags & NSAlternateKeyMask) != 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 incrementMainScreenBrightness: brightnessStep]; + [self incrementMainScreenBrightnessWithStep: brightnessDelta]; }); } } @@ -260,7 +261,7 @@ - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sende return NO; } -- (void)incrementMainScreenBrightness:(int)delta +- (void)incrementMainScreenBrightnessWithStep:(int)deltaInSubsteps { CGDirectDisplayID currentDisplayId = [NSScreen.mainScreen.deviceDescription [@"NSScreenNumber"] unsignedIntValue]; @@ -289,14 +290,24 @@ - (void)incrementMainScreenBrightness:(int)delta isCurrentBrighnessAvailableFromDisplay = get_control(currentDisplayId, BRIGHTNESS, ¤tBrightness, &maxBrightness); } - uint newBrightness = MIN((uint)MAX((int)currentBrightness + delta, 0), maxBrightness); + int currentBrightnessInSubsteps = round((double)currentBrightness / (double)maxBrightness * (double)brightnessSubstepsCount); + + int newBrightnessInSubsteps = MIN(MAX(0, currentBrightnessInSubsteps + deltaInSubsteps), brightnessSubstepsCount); + if (abs(deltaInSubsteps) != 1) { + // newBrightnessInSubsteps must be a multiple of deltaInSubsteps + newBrightnessInSubsteps = (newBrightnessInSubsteps / deltaInSubsteps) * deltaInSubsteps; + } + + uint newBrightness = (uint) round((double)newBrightnessInSubsteps / (double)brightnessSubstepsCount * (double)maxBrightness); if (newBrightness != currentBrightness) { if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { + // NSLog(@"New brightness: %d", newBrightness); + // Display the brighness level OSD - showBrightnessLevelPaneOnDisplay(newBrightness, currentDisplayId); + showBrightnessLevelPaneOnDisplay(newBrightnessInSubsteps, currentDisplayId); if (! isCurrentBrighnessAvailableFromDisplay) { // Save the new brighness value diff --git a/NativeDisplayBrightness/Info.plist b/NativeDisplayBrightness/Info.plist index 607ce92..3a2d448 100644 --- a/NativeDisplayBrightness/Info.plist +++ b/NativeDisplayBrightness/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0.6 + 0.0.7 CFBundleVersion - 19 + 20 LSBackgroundOnly LSMinimumSystemVersion diff --git a/Readme.md b/Readme.md index bccf217..3a9953f 100644 --- a/Readme.md +++ b/Readme.md @@ -4,9 +4,13 @@ ![native brightness UI](https://raw.githubusercontent.com/Bensge/NativeDisplayBrightness/master/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 an external monitor 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 external monitor. If you have an Apple or similar keyboard, you probably need to also press the `fn` key. + +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! It uses the private `BezelServices` framework for this. ## Monitors compatibility @@ -18,6 +22,10 @@ If your monitor supports reading the current brightness value from DDC/CI, the 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. +## Requirements + +macOS version: 10.10 to 10.13 + ## License This application uses code borrowed from [ddcctl](https://github.com/kfix/ddcctl) which uses code from [DDC-CI-Tools](https://github.com/jontaylor/DDC-CI-Tools-for-OS-X) From 2ca9828e02b19d755544cd1594432cdf78228de4 Mon Sep 17 00:00:00 2001 From: Jean-Luc Jumpertz Date: Thu, 10 Jan 2019 12:31:35 +0100 Subject: [PATCH 8/8] Adjust brightness on all connected screens when holding the `shift` key. - Brightness adjustment now targets the main screen if the `shift` key modifier is not pressed, and all screens if `shift` is pressed in conjunction with F1 or F2. - Display the native brightness OSD on every adjusted screen. - Support brightness adjustment of the builtin screen with F1 / F2 for consistency with external screens. --- .../project.pbxproj | 9 +- NativeDisplayBrightness/AppDelegate.m | 116 ++++++++++++------ NativeDisplayBrightness/Info.plist | 4 +- Readme.md | 21 ++-- 4 files changed, 104 insertions(+), 46 deletions(-) diff --git a/NativeDisplayBrightness.xcodeproj/project.pbxproj b/NativeDisplayBrightness.xcodeproj/project.pbxproj index 1f6aa7e..60022a2 100644 --- a/NativeDisplayBrightness.xcodeproj/project.pbxproj +++ b/NativeDisplayBrightness.xcodeproj/project.pbxproj @@ -115,7 +115,7 @@ TargetAttributes = { 9DBE374B1DB7990900ABE422 = { CreatedOnToolsVersion = 8.0; - ProvisioningStyle = Automatic; + ProvisioningStyle = Manual; }; }; }; @@ -265,12 +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; }; @@ -278,12 +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 5696b91..5a36988 100644 --- a/NativeDisplayBrightness/AppDelegate.m +++ b/NativeDisplayBrightness/AppDelegate.m @@ -175,6 +175,7 @@ - (void)_registerGlobalKeyboardEvents 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)) { @@ -186,7 +187,7 @@ - (void)_registerGlobalKeyboardEvents } dispatch_async(dispatch_get_main_queue(), ^{ - [self incrementMainScreenBrightnessWithStep: brightnessDelta]; + [self incrementScreenBrightnessWithStep: brightnessDelta inAllScreens:isShiftModifierPressed]; }); } } @@ -261,75 +262,120 @@ - (BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) sende return NO; } -- (void)incrementMainScreenBrightnessWithStep:(int)deltaInSubsteps +- (void)incrementScreenBrightnessWithStep:(int)deltaInSubsteps inAllScreens:(BOOL)updateAllScreens { - CGDirectDisplayID currentDisplayId = [NSScreen.mainScreen.deviceDescription [@"NSScreenNumber"] unsignedIntValue]; + if (! updateAllScreens) { + // Set the brightness only in the main screen + [self incrementScreenBrightnessWithStep:deltaInSubsteps inScreen:NSScreen.mainScreen]; + } + else { + for (NSScreen* screen in NSScreen.screens) { + [self incrementScreenBrightnessWithStep:deltaInSubsteps inScreen:screen]; + } + } +} + +- (void) incrementScreenBrightnessWithStep:(int)deltaInSubsteps inScreen:(NSScreen*)targetScreen +{ + 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; - if (! CGDisplayIsBuiltin(currentDisplayId)) { + // Get the current brightness + int currentBrightnessInSubsteps = -1; + + if (! isBuiltinDisplay) { uint currentBrightness = 50; - uint maxBrightness = 100; // 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; - NSString* currentDisplayIdKey = [NSString stringWithFormat:@"%u", currentDisplayId]; + currentExternalDisplayDefaultsKey = [NSString stringWithFormat:@"%u", currentDisplayId]; NSDictionary* savedDisplayBrighnesses = [NSUserDefaults.standardUserDefaults objectForKey:kDisplaysBrightnessDefaultsKey]; if ([savedDisplayBrighnesses isKindOfClass:[NSDictionary class]]) { - NSNumber* savedCurrentBrightness = savedDisplayBrighnesses [currentDisplayIdKey]; + NSNumber* savedCurrentBrightness = savedDisplayBrighnesses [currentExternalDisplayDefaultsKey]; if ([savedCurrentBrightness isKindOfClass:[NSNumber class]]) { currentBrightness = (uint) savedCurrentBrightness.unsignedIntegerValue; isCurrentBrighnessReadFromDefaults = YES; } } - BOOL isCurrentBrighnessAvailableFromDisplay = NO; if (! isCurrentBrighnessReadFromDefaults) { - isCurrentBrighnessAvailableFromDisplay = get_control(currentDisplayId, BRIGHTNESS, ¤tBrightness, &maxBrightness); + isCurrentBrighnessAvailableFromExternalDisplay = get_control(currentDisplayId, BRIGHTNESS, ¤tBrightness, &maxExternalDisplayBrightness); } - int currentBrightnessInSubsteps = round((double)currentBrightness / (double)maxBrightness * (double)brightnessSubstepsCount); + 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; } - uint newBrightness = (uint) round((double)newBrightnessInSubsteps / (double)brightnessSubstepsCount * (double)maxBrightness); - - if (newBrightness != currentBrightness) { - - if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { - - // NSLog(@"New brightness: %d", newBrightness); + if (newBrightnessInSubsteps != currentBrightnessInSubsteps) { + // Set the new brightness + if (! isBuiltinDisplay) { + uint newBrightness = (uint) round((double)newBrightnessInSubsteps / (double)brightnessSubstepsCount * (double)maxExternalDisplayBrightness); - // Display the brighness level OSD - showBrightnessLevelPaneOnDisplay(newBrightnessInSubsteps, currentDisplayId); + if (set_control(currentDisplayId, BRIGHTNESS, newBrightness)) { - if (! isCurrentBrighnessAvailableFromDisplay) { - // Save the new brighness value - NSMutableDictionary* newDisplayBrighnesses; - NSDictionary* savedDisplayBrighnesses = [NSUserDefaults.standardUserDefaults objectForKey:kDisplaysBrightnessDefaultsKey]; + // NSLog(@"New brightness: %d", newBrightness); - if ([savedDisplayBrighnesses isKindOfClass:[NSDictionary class]]) { - newDisplayBrighnesses = [NSMutableDictionary dictionaryWithDictionary:savedDisplayBrighnesses]; - } else { - newDisplayBrighnesses = [NSMutableDictionary new]; + 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]; } - - newDisplayBrighnesses [currentDisplayIdKey] = @(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; } } } - else { - // Min or max brightness level: present the OSD to provide a feedback to the user, but don't send a command - showBrightnessLevelPaneOnDisplay(newBrightness, currentDisplayId); - } + + // Display the brighness level OSD + showBrightnessLevelPaneOnDisplay(newBrightnessInSubsteps, currentDisplayId); } } diff --git a/NativeDisplayBrightness/Info.plist b/NativeDisplayBrightness/Info.plist index 3a2d448..435b981 100644 --- a/NativeDisplayBrightness/Info.plist +++ b/NativeDisplayBrightness/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 0.0.7 + 0.0.8 CFBundleVersion - 20 + 21 LSBackgroundOnly LSMinimumSystemVersion diff --git a/Readme.md b/Readme.md index 3a9953f..f9993d3 100644 --- a/Readme.md +++ b/Readme.md @@ -1,30 +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 the brightness of an external monitor directly from your keyboard. +This a utility application to control the brightness of external monitors directly from your keyboard. -Use the `F1` / `F2` key to decrease / increase the brightness of the external monitor. If you have an Apple or similar keyboard, you probably need to also press the `fn` key. +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. 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! It uses the private `BezelServices` framework for this. +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 -## Multiple monitors support +## Implementation notes -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. +For showing the native system UI for brightness adjustment, this app uses the macOS private framework `BezelServices`. ## Requirements -macOS version: 10.10 to 10.13 +macOS version: 10.10 to 10.14 ## License