After talking about it with the panel on a recent Mac Show appearance (pretty sure this part of the conversation happened during technical difficulties), I pulled out my Griffin PowerMate. The older USB one. While I did upgrade to the Bluetooth version, I still like the USB one better1. For various reasons, I’d replaced it with my Shuttle Xpress on my desk, but I found space to comfortably use both and set about re-configuring the actions on it.

Meanwhile, I keep waking up too early and being unable to fall back asleep2. Which means I slog to my computer in the dark, and coming out of a pitch black room means adjusting my monitor brightness more than “automatically adjust brightness” will accommodate. With BetterTouchTool and my Ultimate Hacking Keyboard I have Fn-M and Fn-N assigned to simultaneously increase and decrease the brightness on both of my MacBook Pro display and my external Thunderbolt Display, which is generally satisfactory. But with the PowerMate on my desktop, I developed a curiosity. You can see where this is going.

The thing is, unlike system volume, there’s no System Events API for display brightness. To script it, you have to open System Preferences and use accessibility scripting to actually move the slider, which I refuse to do on principle. It took a while to find a CLI called brightness by Nicholas Riley, but it’s a perfect fix. It can query all displays for current brightness levels (and other info) and modify the brightness on any or all displays from the command line.

brightness can be installed with Homebrew: brew install brightness will do the trick. Once installed, you can use brightness -l to query all displays for basic info:

$ brightness -l
display 0: main, active, awake, online, external, ID 0x4248798
display 0: brightness 1.000000
display 1: active, awake, online, built-in, ID 0x4280886
display 1: brightness 0.937500

Add -v for verbose information:

$ brightness -lv
display 0: main, active, awake, online, external, ID 0x4248798
	resolution 2560 x 1440 pt (2560 x 1440 px) @ 0.0 Hz, origin (0, 0)
	physical size 597 x 336 mm
	IOKit flags 0x2000007; IOKit display mode ID 0x80003000
	usable for desktop GUI, uses OpenGL acceleration
display 0: brightness 1.000000
display 1: active, awake, online, built-in, ID 0x4280886
	resolution 1440 x 900 pt (2880 x 1800 px) @ 0.0 Hz, origin (2560, 586)
	physical size 331 x 207 mm
	IOKit flags 0x2000007; IOKit display mode ID 0x80003000
	usable for desktop GUI, uses OpenGL acceleration
display 1: brightness 0.937500

The current display brightness is returned as a float (decimal) between 0 and 1, so 0.5 is half brightness, and 1 is full. To set a new brightness, just call brightness with a float between 0 and 1 as an argument, e.g. brightness 0.8. You can specify a display with -m for the “main” display or -d and an integer to target a specific display by number (as returned in the -l listing).

In order to put brightness control onto the spinning knob of my PowerMate, I needed to be able to increment and decrement instead of setting the brightness directly. And that’s the only thing that brightness is missing. I started hacking the source to handle this, but because the project is regularly maintained as the SDK changes, I thought twice about messing up its ability to be updated. So a simple script wrapper handles it.

The script below changes the argument from a decimal float to a straight percentage representation (integer between 0 and 100) and allows incrementing and decrementing by percentage.

Using brightness.rb 10 (or +10) will increase the brightness of all attached displays by 10%. Make it negative to decrease, e.g. brightness.rb -10. You can also use = to set a specific percentage, e.g. brightness.rb =75.

To install, just save the script in your path (and optionally make it executable if you’re going to use it from the command line). See below for implementation in other apps.

brightness.rbraw
"
#!/usr/bin/env ruby
# Brett Terpstra 2019
# This script adds the ability to increment/decrement display brightness
# levels to the nriley/brightness CLI (Copyright (c) 2014-2019, Nicholas Riley)
# Requires CLI installation: `brew install brightness`
# See https://github.com/nriley/brightness
CMD = "/usr/local/bin/brightness"

# Display usage and exit
def usage(code)
  puts "Usage: #{File.basename(__FILE__)} [+-=]X"
  puts "  [+-]X increment/decrement by integer 0-100"
  puts "  =X set brightness 0-100"
  Process.exit code
end

# Returns current brightness of the first display as a percentage
# (0-100). All displays will end up set to the same level, so we're only
# checking for the first display.
def get_brightness
  details = %x{#{CMD} -l}

  return (details.match(/display 0: brightness (\d\.\d+)/)[1].to_f.round(2) * 100).to_i
end

# Set brightness of all displays to a percentage (0-100)
def set_brightness(level)
  target = level * 0.01
  return system %Q{"#{CMD}" #{target} &> /dev/null}
end


def main(arg)
  # if the argument starts with =, set brightness
  if arg =~ /^=(\d{1,3})/
    target = $1.to_i

    if target < 0 || target > 100
      puts "Error: #{target} is out of range"
      usage(1)
    end

  # if argument is a positive or negative integer, increment or decrement
  # brightness
  else
    inc = arg.to_i
    current = get_brightness
    target = (current + inc)

    # ensure the adjusted level is in range. Don't throw an error, allow
    # overruns and just compensate
    if target > 100
      target = 100
    elsif target < 0
      target = 0
    end
  end

  # Execute CLI
  res = set_brightness(target)
  if res
    $stdout.print get_brightness
  else
    $stdout.puts "Error setting brightness"
    Process.exit 1
  end
end

# Ensure brightness CLI is installed
unless File.executable?(CMD)
  puts "Error: brightness executable missing"
  puts "Requires brightness CLI (brew install brightness)"
  Process.exit 1
end

# Ensure the argument is valid
if ARGV.length != 1 || ARGV[0] !~ /^[+-=]?\d{1,3}$/
  usage(1)
else
  main(ARGV[0])
end

You can obviously call the script directly where appropriate. The most universal way to incorporate it into most Mac automation utilities is with AppleScript and do shell script:

-- Brightness Up.scpt
do shell script "/usr/bin/env ruby /Users/ttscoff/scripts/brightness.rb 10"

-- Brightness Down.scpt
do shell script "/usr/bin/env ruby /Users/ttscoff/scripts/brightness.rb -10"

(If you’re wondering, always using /usr/bin/env is a future-proofing strategy, given /usr/bin/ruby etc. will be disappearing.)

These AppleScripts can be attached to the PowerMate gestures in the PowerMate app, or used in tools like BetterTouchTool3 to attach the functions to any other hardware, including trackpad gestures, MIDI signals, or your Siri remote. You could incorporate the scripts into a Bunch, too, if you had some reason to do that. I also use them with my Indigo setup so that if I turn on my office light switch during my “night time” hours, my office lights only brighten to 30% and my screens get set to 50%. To that end, my PowerMate now also controls my office lights, because why not?

I blog this partly because I just like to look back years later and see what shenanigans I got up to in the past4. But I have to assume that the lack of automation options for display brightness has been a frustration for others, too, so hopefully this finds some Mac users who will find it helpful.

  1. Aside, why did Griffin cripple the PowerMate app? No more light states or modifier key conditionals, and the AppleScript library cut out all the cool controls… 

  2. I usually get up at 5 but I keep waking up at 3:30am. It’s not new, but for over a year I’ve had much better sleep patterns than I used to and have grown accustomed to 8-hour rest periods. It’s problematic. 

  3. Have you seen BetterTouchTool lately? Hard to believe it continues to get better and better. I really need to write a whole article just about that… 

  4. Plus there’s that thing where I search DDG for an answer to a problem and find out I wrote a solution years ago and then forgot about, which happens to me about once a week. 

BrettTerpstra.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means to earn fees when linking to Amazon.com and affiliated sites.