Skip to content

Instantly share code, notes, and snippets.

@dufferzafar
Forked from waydabber/ddcavcontrol.init.lua
Created July 28, 2022 05:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dufferzafar/b1785eca6b4416bffac15f1b24095c1d to your computer and use it in GitHub Desktop.
Save dufferzafar/b1785eca6b4416bffac15f1b24095c1d 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
-- 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="/usr/local/bin/showosd" -- Get from https://github.com/waydabber/showosd
M1DDC_LOCATION="/usr/local/bin/m1ddc" -- Get from https://github.com/waydabber/m1ddc
-- OS Settings
AV_OS_AUDIO_DEVICE_NAME = "USB Audio Device" -- OS X audio device name to control via network. Leave empty to disable!
DDC_OS_AUDIO_DEVICE_NAME = "LG Ultra HD" -- OS X audio device name to control via DDC
-- Yamaha AV Settings
AV_ADDRESS = "192.168.1.31" -- IP address of the Yamaha AV
AV_INPUT_NAME = "audio1" -- Desired autio input
AV_POWER_INPUT_MANAGEMENT = true -- 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 = 70 -- 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
if event['key'] == "BRIGHTNESS_UP" 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" 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()
if (true) then
if keycode == 144 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 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