Change Laptop and External Monitor Brightness based on cursor position
Posted: 26 Sep 2022, 13:47
Based on where your mouse is, this script will change the brightness of that monitor.
Works with laptop and external monitors.
If you have a laptop monitor, the flyout will also be displayed.
Credits to everyone, because I must have used 5-6+ sources for code. W stands for Window and M stands for mouse. The OSD has been disabled, since it doesn't seem to work on Windows 11 (it just brings up the volume slider)
Works with laptop and external monitors.
If you have a laptop monitor, the flyout will also be displayed.
Credits to everyone, because I must have used 5-6+ sources for code. W stands for Window and M stands for mouse. The OSD has been disabled, since it doesn't seem to work on Windows 11 (it just brings up the volume slider)
Code: Select all
; Use strings "+1" or "-1"
XButton2 & WheelUp :: BrightnessOSDM "+1" ; Cursor Position
XButton2 & WheelDown:: BrightnessOSDM "-1" ; Cursor Position
^Up:: BrightnessOSDW "+1" ; Current Active Window
^Down:: BrightnessOSDW "-1" ; Current Active Window
BrightnessOSDW(b) { ; Use current active window
hMon := DllCall("MonitorFromWindow", "ptr", WinExist("A"), "uint", 0, "ptr")
return BrightnessOSD(hMon, b)
}
BrightnessOSDM(b) { ; Use cursor position
DllCall("GetCursorPos", "uint64*", &point:=0)
hMon := DllCall("MonitorFromPoint", "uint64", point, "uint", 0x2, "ptr")
return BrightnessOSD(hMon, b)
}
BrightnessOSD(hMon, b) { ; https://www.reddit.com/r/AutoHotkey/comments/uvykvk/comment/i9orgso/
MIEX := Buffer(40 + 64)
NumPut("uint", MIEX.size, MIEX)
if DllCall("GetMonitorInfo", "ptr", hMon, "ptr", MIEX)
MonName := StrGet(MIEX.ptr + 40, 32)
; https://stackoverflow.com/a/42351543
; https://social.msdn.microsoft.com/Forums/windows/en-US/d940a189-58e8-48e5-a3f0-0ca0f66f1cb1/howto-get-handle-of-a-display-device-from-displaydevicedevicename?forum=vcgeneral
; struct DISPLAY_DEVICEW - https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-display_devicew
ddAdapter := Buffer(840, 0)
NumPut("uint", ddAdapter.size, ddAdapter)
while DllCall("EnumDisplayDevicesW", "ptr", 0, "uint", A_Index-1, "ptr", ddAdapter, "uint", 0) {
; Useful DISPLAY_DEVICE.StateFlags:
; #define DISPLAY_DEVICE_ATTACHED_TO_DESKTOP 0x00000001
; #define DISPLAY_DEVICE_PRIMARY_DEVICE 0x00000004
; #define DISPLAY_DEVICE_MIRRORING_DRIVER 0x00000008
StateFlags := NumGet(ddAdapter, 324, "uint")
if ((StateFlags & 0x9) == 0x1) {
DeviceName := StrGet(ddAdapter.ptr + 4, 32+2, "UTF-16")
DeviceString := StrGet(ddAdapter.ptr + 68, 128+2, "UTF-16")
ddMonitor := Buffer(840, 0)
NumPut("uint", ddMonitor.size, ddMonitor)
if (DeviceName != MonName)
continue
while DllCall("EnumDisplayDevicesW", "str", DeviceName, "uint", A_Index-1, "ptr", ddMonitor, "uint", 1) { ; EDD_GET_DEVICE_INTERFACE_NAME
; Child Device state
; #define DISPLAY_DEVICE_ACTIVE 0x00000001
; #define DISPLAY_DEVICE_ATTACHED 0x00000002
StateFlags := NumGet(ddMonitor, 324, "uint")
if (StateFlags & 0x1)
{
;DeviceName := StrGet(ddMonitor.ptr + 4, 32+2, "UTF-16")
;DeviceString := StrGet(ddMonitor.ptr + 68, 128+2, "UTF-16")
DeviceID := StrGet(ddMonitor.ptr + 328, 128+2, "UTF-16")
SupportedBrightness := Buffer(256, 0)
Brightness := Buffer(3, 0)
hLCD := DllCall("CreateFile"
, "str", DeviceID ; !!!!!!!!!
, "uint", 0x80000000 | 0x40000000 ;Read | Write
, "uint", 0x1 | 0x2 ; File Read | File Write
, "ptr", 0
, "uint", 0x3 ; open any existing file
, "uint", 0
, "ptr", 0
, "ptr")
if hLCD = -1
throw OSError()
DevVideo := 0x00000023, BuffMethod := 0, Fileacces := 0
NumPut("UChar", 0x03, Brightness, 0) ; 0x01 = Set AC, 0x02 = Set DC, 0x03 = Set both
NumPut("UChar", 0x00, Brightness, 1) ; The AC brightness level
NumPut("UChar", 0x00, Brightness, 2) ; The DC brightness level
DllCall("DeviceIoControl"
, "ptr", hLCD
, "uint", (DevVideo<<16 | 0x126<<2 | BuffMethod<<14 | Fileacces) ; IOCTL_VIDEO_QUERY_DISPLAY_BRIGHTNESS
, "ptr", 0
, "uint", 0
, "ptr", Brightness
, "uint", 3
, "uint*", &BrightnessSize:=0
, "ptr", 0)
DllCall("DeviceIoControl"
, "ptr", hLCD
, "uint", (DevVideo<<16 | 0x125<<2 | BuffMethod<<14 | Fileacces) ; IOCTL_VIDEO_QUERY_SUPPORTED_BRIGHTNESS
, "ptr", 0
, "uint", 0
, "ptr", SupportedBrightness
, "uint", 256
, "uint*", &SupportedBrightnessSize:=0
, "ptr", 0)
ACBrightness := NumGet(Brightness, 1, "UChar")
ACIndex := 0
DCBrightness := NumGet(Brightness, 2, "UChar")
DCIndex := 0
BufferSize := SupportedBrightnessSize
MaxIndex := SupportedBrightnessSize-1
Loop BufferSize
{
ThisIndex := A_Index-1
ThisBrightness := NumGet(SupportedBrightness, ThisIndex, "UChar")
if (ACBrightness = ThisBrightness)
ACIndex := ThisIndex
if (DCBrightness = ThisBrightness)
DCIndex := ThisIndex
}
if (DCIndex >= ACIndex)
BrightnessIndex := DCIndex
else
BrightnessIndex := ACIndex
BrightnessIndex += b
BrightnessIndex := Min(MaxIndex, BrightnessIndex)
BrightnessIndex := Max(0, BrightnessIndex)
NewBrightness := NumGet(SupportedBrightness, BrightnessIndex, "UChar")
NumPut("UChar", 0x03, Brightness, 0) ; 0x01 = Set AC, 0x02 = Set DC, 0x03 = Set both
NumPut("UChar", NewBrightness, Brightness, 1) ; The AC brightness level
NumPut("UChar", NewBrightness, Brightness, 2) ; The DC brightness level
; will set A_LastError to ERROR_INVALID_HANDLE (0x6) if not a built in LCD.
check := DllCall("DeviceIoControl"
, "ptr", hLCD
, "uint", (DevVideo<<16 | 0x127<<2 | BuffMethod<<14 | Fileacces) ; IOCTL_VIDEO_SET_DISPLAY_BRIGHTNESS
, "ptr", Brightness
, "uint", 3
, "ptr", 0
, "uint", 0
, "ptr", 0
, "ptr", 0)
DllCall("CloseHandle", "ptr", hLCD)
if not check {
static stored := Map()
DllCall("dxva2\GetNumberOfPhysicalMonitorsFromHMONITOR"
, "ptr", hMon
, "uint*", &PhysMons:=0)
DllCall("dxva2\GetPhysicalMonitorsFromHMONITOR"
, "ptr", hMon
, "uint", PhysMons
, "ptr", PHYS_MONITORS := Buffer((A_PtrSize + 256) * PhysMons, 0))
hPhysMon := NumGet(PHYS_MONITORS, 0, "ptr")
start := A_TickCount
; Occasionaly fails to communicate with the monitor, setting current brightness to zero.
; fails if not external
if ! stored.has(MonName) {
; fixes a bug where the call randomly fails :(
while !DllCall("dxva2\GetVCPFeatureAndVCPFeatureReply"
, "Ptr", hPhysMon
,"uchar", 0x10
, "ptr", 0
, "uint*", &BrightnessCur:=0
, "uint*", &BrightnessMax:=0)
if A_Index >= 6
throw OSError()
b := Max(0, Min(BrightnessMax, BrightnessCur + b))
stored[MonName] := [0, b, BrightnessMax]
} else {
BrightnessMax := stored[MonName][3]
BrightnessCur := stored[MonName][2]
b := stored[MonName][2] := Max(0, Min(BrightnessMax, BrightnessCur + b))
}
; lmao you need to create a named function, and use named_func.bind or else SetTimer will just reset the timer!
set_brightness(b, bref, xd2) {
if (b == %bref%)
DllCall("dxva2\SetVCPFeature", "Ptr",hPhysMon, "uchar", 0x10, "UInt", b)
DllCall("dxva2\DestroyPhysicalMonitors", "uint", PhysMons, "ptr", xd2)
}
; Sending a reference to b in case it changes is pretty important, as it allows updating the timer's function queue
; by discarding the unnecessary changes. For example, when going from 30 → 50, it's not necessary to set 31, 32, 33,
; if the current value of b is already at 50. Instead it may set 31, return, and see a new value of 50, skip 32-49 and set 50.
SetTimer set_brightness.bind(b, &b, PHYS_MONITORS), -1
}
; Cause you used createfile
else {
/*
; Show the Brightness OSD
static WM_SHELLHOOK := DllCall("RegisterWindowMessage", "Str", "SHELLHOOK", "UInt")
static hwnd := 0
if !hwnd
try if shellProvider := ComObject("{C2F03A33-21F5-47FA-B4BB-156362A2F239}", "{00000000-0000-0000-C000-000000000046}")
try if flyoutDisp := ComObjQuery(shellProvider, "{41f9d2fb-7834-4ab6-8b1b-73e74064b465}", "{41f9d2fb-7834-4ab6-8b1b-73e74064b465}")
if !ComCall(3, flyoutDisp, "int", 0, "uint", 0)
hwnd := DllCall("FindWindow", "Str", "NativeHWNDHost", "Str", "", "Ptr")
DllCall("PostMessage", "ptr", hwnd, "uint", WM_SHELLHOOK, "uptr", 0x37, "ptr", 0)
*/
}
}
}
}
}
}
Old Code
Windows has too many monitor APIs, and this approach combines all of them.
- EnumDisplayDevices - Reliable, yet also broken. Most of the symlinks it purports to create are not actually created, however, I managed to get this working.
- EnumDisplayMonitors - Works, except cannot be used with CreateFile to open the device. Only returns an hMonitor
- CreateFile - Can open a monitor device and returns a device handle compatible with DeviceIoControl.
- DeviceIoControl - Extremely fast. Only works with laptop monitors.
- GetPhysicalMonitorsFromHMONITOR - Uses the Physical Monitor API in combination with VCP codes.
- SetVCPFeature - Slow. Only works with external monitors. Has a delay of about 50-100 ms, as it actually communicates with hardware.
- PowerWriteACValueIndex - Indirectly sets the laptop brightness through the power plan.
- ComObjGet( "winmgmts:\\.\root\WMI" ).ExecQuery("SELECT * FROM WmiMonitorBrightnessMethods") - Indirect. Executes a SQL Query, the absolute slowest. Extremely reliable however.
- Clean up code
- Optimize - SetVCP is already optimized, the 40ms delay cannot be reduced any further, as I've tested it against TwinkleTray. TwinkleTray uses a delay to send only the latest VCP command, so it seems faster, this function seems slower, because every value from 80 → 40 is sent.