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.