Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Key bindings questions #4

Open
Cayprol opened this issue Aug 3, 2017 · 5 comments
Open

Key bindings questions #4

Cayprol opened this issue Aug 3, 2017 · 5 comments

Comments

@Cayprol
Copy link

Cayprol commented Aug 3, 2017

Is it possible to make it also work on Apple keyboard where F1/F2 are brightness dim.
I found no matching kVK key code for these two keys tho.

@Bensge
Copy link
Owner

Bensge commented Aug 7, 2017

It is possible to accomplish this, but not by simply using a different key code. The special function keys are handled differently by the system. Some special function keys can be intercepted by passing NX_SYSDEFINED as eventsOfInterest to CGEventTapCreate, but it doesn't work for the brightness keys (at least on my machine).
This could be solved by getting the HID events directly from IOKit, but that would create a whole lot of other problems.
This is what I recommend right now:

  • If you're using clover bootloader, try this patch which remaps the brightness keys to F1/F2 permanently
  • Alternatively, use FunctionFlip to remap the brightness keys to F1/F2
  • If you don't want to install additional software, just use Function+F1/F2 to change the brightness (Function + special key is remapped to F1, F2 etc by default

@Cayprol
Copy link
Author

Cayprol commented Aug 8, 2017

Hey, thank you for the effort and explanation.
I will test out your suggestions for clover and 3rd party key mapping software.
Although the reason why I am looking into your code is a bit more complex.
By remapping system keys to Function keys won't be ideal since my goal is to use both types of keys on certain softwares.
My google search didn't really find any solution or working example of detecting Brightness keys, I will try to look into IOKit, thank you very much for the info.

@CharlesButcher
Copy link

Many thanks for the link to FunctionFlip. It's working nicely with NativeDisplayBrightness under Mojave on my Mac mini 2011 and Samsung S24F350FHU monitor.

@GenuineJakob
Copy link

There is also this reddit post:
Use the real brightness keys on a real Apple keyboard for a third-party, external monitor
It describes how to remap the keys with Karabiner-Elements.

But I would still prefer to have it natively coded into the application. Did anybody had success with that?

@krackers
Copy link

It seems to work for me with cgeventtap on nx_sys events. For reference, monitorControl also does the same thing so it should definitely work. I modified nativebrightness to support this, and also added volume support: it works well for me

//
//  AppDelegate.m
//  NativeDisplayBrightness
//
//  Created by Benno Krauss on 19.10.16.
//  Copyright © 2016 Benno Krauss. All rights reserved.
//

#import "AppDelegate.h"
#import "DDC.h"
#import "BezelServices.h"
#import "OSD.h"
#include <dlfcn.h>
@import Carbon;

#pragma mark - constants

static NSString *brightnessValuePreferenceKey = @"brightness";
static NSString *volumeValuePreferenceKey = @"volume";
static const float brightnessStep = 100 / 16.f;
static const float volumeStep = 100 / 16.f;

#pragma mark - variables

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) {
  struct DDCWriteCommand command;
  command.control_id = control_id;
  command.new_value = new_value;
  printf("New value %d\n", new_value);

  if (!DDCWrite(cdisplay, &command)) {
    NSLog(@"E: Failed to send DDC command!");
  }
}

static int mk_code(NSEvent *event) {
  return (([event data1] & 0xFFFF0000) >> 16);
}

static int mk_flags(NSEvent *event) { return ([event data1] & 0x0000FFFF); }

static int mk_down(NSEvent *event) {
  return (((mk_flags(event) & 0xFF00) >> 8)) == 0xA;
}

CGEventRef keyboardCGEventCallback(CGEventTapProxy proxy, CGEventType type,
                                   CGEventRef event, void *refcon) {
  NSEvent *nsevent = [NSEvent eventWithCGEvent:event];
  if ([nsevent type] != NSSystemDefined)
    return event;

  if ([nsevent subtype] == 8) {
    if (!mk_down(nsevent)) {
      return event;
    }
    int keycode = mk_code(nsevent);
    printf("Keycode: %d\n", keycode);

    if (keycode == NX_KEYTYPE_SOUND_DOWN) {
      printf("Volume down\n");
      dispatch_async(dispatch_get_main_queue(), ^{
        [(__bridge AppDelegate *)refcon decreaseVolume];
      });
      return NULL;
    }

    if (keycode == NX_KEYTYPE_SOUND_UP) {
      printf("Volume up\n");
      dispatch_async(dispatch_get_main_queue(), ^{
        [(__bridge AppDelegate *)refcon increaseVolume];
      });
      return NULL;
    }

    if (keycode == NX_KEYTYPE_BRIGHTNESS_DOWN) {
      printf("Brightness down\n");
      dispatch_async(dispatch_get_main_queue(), ^{
        [(__bridge AppDelegate *)refcon decreaseBrightness];
      });
      return NULL;
    }

    if (keycode == NX_KEYTYPE_BRIGHTNESS_UP) {
      printf("Brightness up\n");
      // filter eject key when no modifiers are pressed
      dispatch_async(dispatch_get_main_queue(), ^{
        [(__bridge AppDelegate *)refcon increaseBrightness];
      });
      return NULL;
    }
  }
  return event;
}

#pragma mark - AppDelegate

@interface AppDelegate ()

@property(weak) IBOutlet NSWindow *window;
@property(nonatomic) float brightness;
@property(nonatomic) float volume;
@property(strong, nonatomic) dispatch_source_t signalHandlerSource;
@end

@implementation AppDelegate

- (BOOL)_loadBezelServices {
  // Load BezelServices framework
  void *handle = dlopen("/System/Library/PrivateFrameworks/"
                        "BezelServices.framework/Versions/A/BezelServices",
                        RTLD_GLOBAL);
  if (!handle) {
    NSLog(@"Error opening framework");
    return NO;
  } else {
    _BSDoGraphicWithMeterAndTimeout =
        dlsym(handle, "BSDoGraphicWithMeterAndTimeout");
    return _BSDoGraphicWithMeterAndTimeout != NULL;
  }
}

- (BOOL)_loadOSDFramework {
  return [[NSBundle
      bundleWithPath:@"/System/Library/PrivateFrameworks/OSD.framework"] load];
}

- (void)_configureLoginItem {
  NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
  LSSharedFileListRef loginItemsListRef =
      LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
  NSDictionary *properties = @{ @"com.apple.loginitem.HideOnLaunch" : @YES };
  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 {

  CFRunLoopRef runloop = (CFRunLoopRef)CFRunLoopGetCurrent();
  CGEventMask interestedEvents = CGEventMaskBit(NX_SYSDEFINED);
  CFMachPortRef eventTap = CGEventTapCreate(
      kCGSessionEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault,
      interestedEvents, keyboardCGEventCallback, (__bridge void *)(self));
  // by passing self as last argument, you can later send events to this class
  // instance

  CFRunLoopSourceRef source =
      CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
  CFRunLoopAddSource((CFRunLoopRef)runloop, source, kCFRunLoopCommonModes);

  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]) {
    [self _loadOSDFramework];
  }
  //[self _configureLoginItem];
  [self _checkTrusted];
  [self _registerGlobalKeyboardEvents];
  [self _loadBrightness];
  [self _registerSignalHandling];
}

void shutdownSignalHandler(int signal) {
  // Don't do anything
}

- (void)_registerSignalHandling {
  // Register signal callback that will gracefully shut the application down
  self.signalHandlerSource = dispatch_source_create(
      DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue());
  dispatch_source_set_event_handler(self.signalHandlerSource, ^{
    NSLog(@"Caught SIGTERM");
    [[NSApplication sharedApplication] terminate:self];
  });
  dispatch_resume(self.signalHandlerSource);
  // Register signal handler that will prevent the app from being killed
  signal(SIGTERM, shutdownSignalHandler);
}

- (void)applicationWillTerminate:(NSNotification *)aNotification {
  [self _willTerminate];
}

- (void)_willTerminate {
  NSLog(@"willTerminate");
  [self _saveBrightness];
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:
        (NSApplication *)sender {
  return NO;
}

dispatch_source_t debounceTimer = nil;

dispatch_source_t CreateDebounceDispatchTimer(double debounceTime,
                                              dispatch_queue_t queue,
                                              dispatch_block_t block) {
  dispatch_source_t timer =
      dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

  if (timer) {
    dispatch_source_set_timer(
        timer, dispatch_time(DISPATCH_TIME_NOW, debounceTime * NSEC_PER_SEC),
        DISPATCH_TIME_FOREVER, (1ull * NSEC_PER_SEC) / 10);
    dispatch_source_set_event_handler(timer, block);
    dispatch_resume(timer);
  }

  return timer;
}

- (void)setBrightness:(float)value {
  _brightness = value;

  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];
  }

  if (debounceTimer != nil) {
    dispatch_source_cancel(debounceTimer);
    debounceTimer = nil;
  }
  dispatch_queue_t queue =
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  double secondsToThrottle = 0.01f;
  debounceTimer = CreateDebounceDispatchTimer(secondsToThrottle, queue, ^{
    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);
      }
    }
  });
}

- (void)setVolume:(float)value {
  _volume = value;

  CGDirectDisplayID display = CGSMainDisplayID();

  if (_BSDoGraphicWithMeterAndTimeout != NULL) {
    // El Capitan and probably older systems
    _BSDoGraphicWithMeterAndTimeout(display, BSGraphicSpeakerMeter, 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];
  }

  if (debounceTimer != nil) {
    dispatch_source_cancel(debounceTimer);
    debounceTimer = nil;
  }
  dispatch_queue_t queue =
      dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  double secondsToThrottle = 0.01f;
  debounceTimer = CreateDebounceDispatchTimer(secondsToThrottle, queue, ^{
    for (NSScreen *screen in NSScreen.screens) {
      NSDictionary *description = [screen deviceDescription];
      if ([description objectForKey:@"NSDeviceIsScreen"]) {
        CGDirectDisplayID screenNumber =
            [[description objectForKey:@"NSScreenNumber"] unsignedIntValue];

        set_control(screenNumber, AUDIO_SPEAKER_VOLUME,
                    MIN(ceil(100.0 * pow(value / 100.0, 2)), 20));
      }
    }
  });
}

- (void)increaseBrightness {
  self.brightness = MIN(self.brightness + brightnessStep, 100);
}

- (void)decreaseBrightness {
  self.brightness = MAX(self.brightness - brightnessStep, 0);
}

- (void)increaseVolume {
  self.volume = MIN(self.volume + volumeStep, 100);
}

- (void)decreaseVolume {
  self.volume = MAX(self.volume - volumeStep, 0);
}

@end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants