-
-
Save vladborovtsov/46c0fc415be101d8db9b0ece6e386b80 to your computer and use it in GitHub Desktop.
Hammerspoon script to control a Display via DDC (brighness, volume) and a Yamaha AV (network) using standard Mac keyboard with MacOS OSD; Modified: don't intercept events when active display is builtin retina
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- ddcavcontrol | |
-- v1.2.1 | |
-- This Hammerspoon script is intended to do the following: | |
-- 1) Control External Display Brightness via DDC (utilizing a proper brightness+contrast curve) | |
-- 2) Control External Display Volume via DDC | |
-- 3) Control Digital AV Volume via Network (currently works with Yamaha AVs) | |
-- 4) Use the standard brightness and volume keys of an Apple keyboards | |
-- 5) Display the standard MacOS OSD as expected | |
-- Use the Volume Up/Down and Mute keys to control the volume! | |
-- Use the Brightness Up/Down to control brightness! | |
-- You can use Shift+Option+Volume Up/Down for fine control! | |
-- Using the Volume and Mute keys turns on the AV and sets correct input as well. | |
-- Press SHIFT+Mute to put the AV to StandBy | |
-- This script requires the waydabber's showosd to display the MacOS OSD! | |
-- Installation: | |
-- 1. Get Hammerspoon from https://github.com/Hammerspoon/hammerspoon | |
-- 2. Get showosd from https://github.com/waydabber/showosd and put it into /usr/local/bin | |
-- 3. Get m1ddc from https://github.com/waydabber/mm1ddc and put it into /usr/local/bin | |
-- 4. Select 'Open Config' in Hammerspoon's menu and copy the contents of this script into init.lua | |
-- 5. Modify the Settings below to fit your configuration | |
-- 6. Select 'Reload Config' in Hammerspoon's menu | |
-- External tool locations | |
SHOWOSD_LOCATION="~/.hammerspoon/showosd/.build/release/showosd" -- Get from https://github.com/waydabber/showosd | |
M1DDC_LOCATION="~/.hammerspoon/m1ddc/m1ddc" -- Get from https://github.com/waydabber/m1ddc | |
-- OS Settings | |
AV_OS_AUDIO_DEVICE_NAME = "" -- OS X audio device name to control via network. Leave empty to disable! | |
DDC_OS_AUDIO_DEVICE_NAME = "" -- OS X audio device name to control via DDC | |
-- Yamaha AV Settings | |
AV_ADDRESS = "" -- IP address of the Yamaha AV | |
AV_INPUT_NAME = "audio1qweqwe" -- Desired autio input | |
AV_POWER_INPUT_MANAGEMENT = false -- Set to false to turn off AV power and input control | |
AV_MAX_VOLUME = 60 -- Limit Max AV volume (AV should be in to Scale mode, not dB mode) | |
-- DDC Display settings | |
DISPLAY_MAX_DDC_BRIGHTNESS = 100 -- DDC limit (check with 'm1ddc max brightness') - usually 100 | |
DISPLAY_MAX_DDC_CONTRAST = 100 -- DDC limit (check with 'm1ddc max contrast') - usually 100 | |
DISPLAY_MAX_DDC_VOLUME = 100 -- DDC limit (check with 'm1ddc max volume') - usually 100 | |
DISPLAY_OPTIMAL_CONTRAST = 80 -- Calibrated limit via the Display's own OSD that still does not distort image - usually 70-80 | |
DISPLAY_MINIMAL_CONTRAST = 40 -- Calibrated limit via the Display's own OSD that still makes sense - usually 20-30 | |
-- OSD Constants (don't change) | |
VOLUME_SEGMENTS = 16 | |
VOLUME_INCREMENT_MINOR = 1 | |
VOLUME_INCREMENT = VOLUME_INCREMENT_MINOR*4 | |
VOLUME_MAX = VOLUME_SEGMENTS * VOLUME_INCREMENT | |
BRIGHTNESS_SEGMENTS = 16 | |
BRIGHTNESS_INCREMENT_MINOR = 1 | |
BRIGHTNESS_INCREMENT = BRIGHTNESS_INCREMENT_MINOR * 4 | |
BRIGHTNESS_MAX = BRIGHTNESS_SEGMENTS * BRIGHTNESS_INCREMENT | |
-- Implementation | |
isMuted = false | |
currentAVVolume = VOLUME_INCREMENT * 4 | |
currentDDCVolume = VOLUME_INCREMENT * 4 | |
currentBrightness = BRIGHTNESS_INCREMENT * 12 | |
prevDdcBrightness=0 | |
prevDdcContrast=0 | |
prevDdcOSTime=0 | |
-- Set Yamaha AV audio via HTTP | |
function setAVVolume(showOSD) | |
if currentAVVolume<0 then currentAVVolume=0 end | |
if currentAVVolume>VOLUME_MAX then currentAVVolume=VOLUME_MAX end | |
if showOSD then | |
if currentAVVolume == 0 then | |
hs.execute(SHOWOSD_LOCATION .. " mute 0") | |
else | |
hs.execute(SHOWOSD_LOCATION .. " volume " .. math.floor(currentAVVolume) .. " " .. VOLUME_MAX) | |
end | |
end | |
if (AV_POWER_INPUT_MANAGEMENT) then | |
hs.http.doRequest("http://" .. AV_ADDRESS .. "/YamahaExtendedControl/v1/main/setPower?power=on","GET") | |
hs.http.doRequest("http://" .. AV_ADDRESS .. "/YamahaExtendedControl/v1/main/setInput?input=" .. AV_INPUT_NAME,"GET") | |
end | |
local avVolume=math.floor((currentAVVolume/VOLUME_MAX)*AV_MAX_VOLUME*2) | |
hs.http.doRequest("http://" .. AV_ADDRESS .. "/YamahaExtendedControl/v1/main/setVolume?volume=" .. avVolume,"GET") | |
end | |
-- Set display audio via DDC | |
function setDDCVolume(showOSD) | |
if currentDDCVolume<0 then currentDDCVolume=0 end | |
if currentDDCVolume>VOLUME_MAX then currentDDCVolume=VOLUME_MAX end | |
if showOSD then | |
if currentDDCVolume == 0 then | |
hs.execute(SHOWOSD_LOCATION .. " mute 0") | |
else | |
hs.execute(SHOWOSD_LOCATION .. " volume " .. math.floor(currentDDCVolume) .. " " .. VOLUME_MAX) | |
end | |
end | |
local ddcVolume=math.floor((currentDDCVolume/VOLUME_MAX)*DISPLAY_MAX_DDC_VOLUME) | |
hs.execute(M1DDC_LOCATION .. " set volume " .. ddcVolume) | |
end | |
-- Set display brightness via DDC | |
function setBrightness(showOSD) | |
if currentBrightness<0 then currentBrightness=0 end | |
if currentBrightness>BRIGHTNESS_MAX then currentBrightness=BRIGHTNESS_MAX end | |
if showOSD then | |
hs.execute(SHOWOSD_LOCATION .. " brightness " .. math.floor(currentBrightness) .. " " .. BRIGHTNESS_MAX) | |
end | |
-- You can modify the brighness and contrast curves to fit your display better here: | |
BRIGHTNESS_TO_CONTRAST_TRESHOLD = BRIGHTNESS_INCREMENT * 4 -- The point after which contrast is decreased instead of brightness | |
local calculatedBrightness = (math.max(currentBrightness-BRIGHTNESS_TO_CONTRAST_TRESHOLD,0)/(BRIGHTNESS_MAX-BRIGHTNESS_TO_CONTRAST_TRESHOLD)*100)^1.5/10 | |
local calculatedContrast = 100+math.min(currentBrightness-BRIGHTNESS_TO_CONTRAST_TRESHOLD,0)/(BRIGHTNESS_TO_CONTRAST_TRESHOLD)*100 | |
-- | |
local ddcBrightness = math.floor(calculatedBrightness/100*DISPLAY_MAX_DDC_BRIGHTNESS) | |
local ddcContrast = math.floor((calculatedContrast/100*(DISPLAY_OPTIMAL_CONTRAST-DISPLAY_MINIMAL_CONTRAST)+DISPLAY_MINIMAL_CONTRAST)) | |
if prevDdcBrightness ~= ddcBrightness or os.time()-prevDdcOSTime > 1 then hs.execute(M1DDC_LOCATION .. " set luminance " .. ddcBrightness) end | |
if prevDdcContrast ~= ddcContrast or os.time()-prevDdcOSTime > 1 then hs.execute(M1DDC_LOCATION .. " set contrast " .. ddcContrast) end | |
prevDdcBrightness = ddcBrightness | |
prevDdcContrast = ddcContrast | |
prevDdcOSTime = os.time() | |
end | |
-- Event trap for the Apple Keyboard's brightness and volume keys | |
systemeventtap = hs.eventtap.new({hs.eventtap.event.types.systemDefined}, function(mainEvent) | |
local event = mainEvent:systemKey() | |
local flags = hs.eventtap.checkKeyboardModifiers() | |
if event['down'] == false or event['repeat'] == true then | |
if hs.audiodevice.defaultOutputDevice():name() == AV_OS_AUDIO_DEVICE_NAME then | |
if (event['key'] == "MUTE") then | |
if (flags['shift'] or flags['alt'] or flags['cmd'] or flags['ctrl']) and AV_POWER_INPUT_MANAGEMENT then | |
hs.execute(SHOWOSD_LOCATION .. " mutedisable") | |
hs.http.doRequest("http://" .. AV_ADDRESS .. "/YamahaExtendedControl/v1/main/setPower?power=standby","GET") | |
elseif isMuted == false then | |
isMuted = true | |
hs.execute(SHOWOSD_LOCATION .. " mute 0") | |
setAVVolume(false) | |
hs.http.doRequest("http://" .. AV_ADDRESS .. "/YamahaExtendedControl/v1/main/setMute?enable=true","GET") | |
else | |
isMuted = false | |
setAVVolume(true) | |
end | |
return true | |
end | |
if event['key'] == "SOUND_UP" then | |
if flags['shift'] and flags['alt'] then currentAVVolume = currentAVVolume+VOLUME_INCREMENT_MINOR | |
else currentAVVolume = math.floor((currentAVVolume)/VOLUME_INCREMENT)*VOLUME_INCREMENT+VOLUME_INCREMENT | |
end | |
setAVVolume(true) | |
return true | |
end | |
if event['key'] == "SOUND_DOWN" then | |
if flags['shift'] and flags['alt'] then currentAVVolume = currentAVVolume-VOLUME_INCREMENT_MINOR | |
else currentAVVolume = math.floor((currentAVVolume-VOLUME_INCREMENT_MINOR)/VOLUME_INCREMENT)*VOLUME_INCREMENT | |
end | |
setAVVolume(true) | |
return true | |
end | |
end | |
if hs.audiodevice.defaultOutputDevice():name() == DDC_OS_AUDIO_DEVICE_NAME then | |
if (event['key'] == "MUTE") then | |
if isMuted == false then | |
isMuted = true | |
hs.execute(SHOWOSD_LOCATION .. " mute 0") | |
hs.execute(M1DDC_LOCATION .. " set mute on") | |
else | |
isMuted = false | |
hs.execute(M1DDC_LOCATION .. " set mute off") | |
setDDCVolume(true) | |
end | |
return true | |
end | |
if event['key'] == "SOUND_UP" then | |
if flags['shift'] and flags['alt'] then currentDDCVolume = currentDDCVolume+VOLUME_INCREMENT_MINOR | |
else currentDDCVolume = math.floor((currentDDCVolume)/VOLUME_INCREMENT)*VOLUME_INCREMENT+VOLUME_INCREMENT | |
end | |
setDDCVolume(true) | |
return true | |
end | |
if event['key'] == "SOUND_DOWN" then | |
if flags['shift'] and flags['alt'] then currentDDCVolume = currentDDCVolume-VOLUME_INCREMENT_MINOR | |
else currentDDCVolume = math.floor((currentDDCVolume-VOLUME_INCREMENT_MINOR)/VOLUME_INCREMENT)*VOLUME_INCREMENT | |
end | |
setDDCVolume(true) | |
return true | |
end | |
end | |
local mainScreen = hs.screen.primaryScreen():name() | |
if event['key'] == "BRIGHTNESS_UP" and mainScreen ~= "Built-in Retina Display" then | |
if flags['shift'] and flags['alt'] then currentBrightness = currentBrightness+BRIGHTNESS_INCREMENT_MINOR | |
else currentBrightness = math.floor((currentBrightness)/BRIGHTNESS_INCREMENT)*BRIGHTNESS_INCREMENT+BRIGHTNESS_INCREMENT | |
end | |
setBrightness(true) | |
return true | |
end | |
if event['key'] == "BRIGHTNESS_DOWN" and mainScreen ~= "Built-in Retina Display" then | |
if flags['shift'] and flags['alt'] then currentBrightness = currentBrightness-BRIGHTNESS_INCREMENT_MINOR | |
else currentBrightness = math.floor((currentBrightness-BRIGHTNESS_INCREMENT_MINOR)/BRIGHTNESS_INCREMENT)*BRIGHTNESS_INCREMENT | |
end | |
setBrightness(true) | |
return true | |
end | |
end | |
end) | |
systemeventtap:start() | |
-- Event trap for the Bluetooth Apple Keyboard's brightness keys | |
keyeventtap = hs.eventtap.new({hs.eventtap.event.types.keyDown}, function(mainEvent) | |
local keycode = mainEvent:getKeyCode() | |
local flags = hs.eventtap.checkKeyboardModifiers() | |
local mainScreen = hs.screen.primaryScreen():name() | |
if (true) then | |
if keycode == 144 and mainScreen ~= "Built-in Retina Display" then | |
if flags['shift'] and flags['alt'] then currentBrightness = currentBrightness+BRIGHTNESS_INCREMENT_MINOR | |
else currentBrightness = math.floor((currentBrightness)/BRIGHTNESS_INCREMENT)*BRIGHTNESS_INCREMENT+BRIGHTNESS_INCREMENT | |
end | |
setBrightness(true) | |
return true | |
end | |
if keycode == 145 and mainScreen ~= "Built-in Retina Display" then | |
if flags['shift'] and flags['alt'] then currentBrightness = currentBrightness-BRIGHTNESS_INCREMENT_MINOR | |
else currentBrightness = math.floor((currentBrightness-BRIGHTNESS_INCREMENT_MINOR)/BRIGHTNESS_INCREMENT)*BRIGHTNESS_INCREMENT | |
end | |
setBrightness(true) | |
return true | |
end | |
end | |
end) | |
keyeventtap:start() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment