It seems that versions of Windows prior to Windows 10 do not have a power management option to turn off a display when a lid is closed.
Usually it’s not a problem. Either it is managed by hardware/bios (as it is on my Acer C720) or such behavior is written in drivers by laptop manufacturer (power management or video drivers, I don’t know).
The problem may arise when you dared to go against the will of the manufacturer and are trying to use “some” unsupported operating system (it is usually also has not quite legal origin, but this is another question 🙂 ).
If bios handles display’s backlight depending on lid position – there is no problem, but on my DELL 3490 that’s not the case.
I’m trying to use Windows 7 on this machine and it works almost well. Much better than it could be expected for hardware which support for Windows 7 has been deliberately mangled by Intel. But that’s the topic for the next article. For now I just want to automatically switch off internal display when the lid is closed (and turn it on again, when lid is opened).
The laptop has all necessary sensors but they are not managed by EFI or video/power management drivers – so the display’s backlight remains activated in closed laptop (interestingly enough, keyboard backlight apparently is managed by EFI).
This is not a particularly big problem because power consumption of the display is about 1.5 – 2W in my usage pattern, but it’s irritating 🙂
First, I found on the Internet this powershell script.
powershell -WindowStyle Hidden -NonInteractive (Add-Type '[DllImport(\"user32.dll\")]^public static extern int SendMessage(int hWnd, int hMsg, int wParam, int lParam);' -Name a -Pas)::SendMessage(-1,0x0112,0xF170,2) |
I don’t understand what is written in it 🙂 but it works. I put this line into a *.bat file and used it for some time, but this script must be run manually. There is no “lid closed” or “lid opened” events in Windows System Event Log so it is impossible to use Windows Scheduler to run this script automatically (as far as I know).
I tried to run this script when I lock the laptop (this event could be enabled in System Event Log and therefore one may attach a script to it using Scheduler), but script ceased to work in this case. No errors were reported no screen was switched off.
Then I checked autoit forums and managed to create a Frankenstein’s script from various posts.
Autoit script source code
#include <GUIConstantsEx.au3> Opt("TrayOnEventMode", 1) ; Use event trapping for tray menu Opt("TrayMenuMode", 3) ; Default tray menu items will not be shown. Opt("WinTitleMatchMode", 4); $hTray_Show_Item = TrayCreateItem("Hide") TrayItemSetOnEvent(-1, "To_Tray") TrayCreateItem("") TrayCreateItem("Exit") TrayItemSetOnEvent(-1, "On_Exit") TraySetIcon(@SystemDir & "\Shell32.dll", -175) Global Const $WM_POWERBROADCAST = 0x0218 ; GUID_LIDSWITCH_STATE_CHANGE GUID ; 0xBA3E0F4D, 0xB817, 0x4094, 0xA2, 0xD1, 0xD5, 0x63, 0x79, 0xE6, 0xA0, 0xF3 Global Const $GUID_LIDSWITCH_STATE_CHANGE = DllStructCreate("byte[16]") DllStructSetData($GUID_LIDSWITCH_STATE_CHANGE, 1, Binary("0x4D0F3EBA17B89440A2D1D56379E6A0F3")) ; create gui and register notification Global $GUI = GUICreate("Laptop Lid Monitor", 300, 150) GUIRegisterMsg($WM_POWERBROADCAST, "__WM_POWER") Global $hPower = DllCall("user32.dll", "handle", "RegisterPowerSettingNotification", "handle", $GUI, "ptr", DllStructGetPtr($GUID_LIDSWITCH_STATE_CHANGE), "dword", 0) GUISetIcon(@SystemDir & "\Shell32.dll", -175) GUISetState() Sleep(2000) To_Tray() ;Do ;Until GUIGetMsg() = -3 While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE Exit Case $GUI_EVENT_MINIMIZE To_Tray() EndSwitch WEnd ; unregister notification DllCall("user32.dll", "bool", "UnregisterPowerSettingNotification", "handle", $hPower[0]) Func __WM_POWER($hwnd, $msg, $wparam, $lparam) #forceref $hwnd Local Const $PBT_POWERSETTINGCHANGE = 0x8013 ; Data member is variable depending on notification ; the size of the Data member is returned in DataLength Local Const $POWERBROADCAST_SETTING = "byte[16];dword DataLength;byte Data" Switch $msg Case $WM_POWERBROADCAST Switch $wparam Case $PBT_POWERSETTINGCHANGE Local $PBT_PSC = DllStructCreate($POWERBROADCAST_SETTING, $lparam) ; GUID of registered notification is the first member of the structure Switch DllStructGetData($PBT_PSC, 1) Case DllStructGetData($GUID_LIDSWITCH_STATE_CHANGE, 1) ; for this notification, Data is an int representing the lid state Local $data = DllStructCreate("int", DllStructGetPtr($PBT_PSC, "Data")) _LidStateChange(DllStructGetData($data, 1)) ; Return 1 EndSwitch EndSwitch EndSwitch Return "GUI_RUNDEFMSG" EndFunc Func _LidStateChange($iState) Static $iPrevious = -1 If $iPrevious = -1 Then ; first fire, current state $iPrevious = $iState Return ElseIf $iPrevious = $iState Then Return Else $iPrevious = $iState EndIf ; lid state: ; 0 -> closed ; 1 -> open Switch $iState Case 0 ;ConsoleWrite("lid closed" & @CRLF) ;DllCall("user32.dll", "bool", "LockWorkStation") Monitor("off") Case 1 ;ConsoleWrite("lid opened" & @CRLF) Monitor("on") EndSwitch EndFunc Func Monitor($io_control = "on") Local $WM_SYSCommand = 274 Local $SC_MonitorPower = 61808 Local $HWND = WinGetHandle("classname=Progman") Switch StringUpper($io_control) Case "OFF" DllCall("user32.dll", "int", "SendMessage", "hwnd", $HWND, "int", $WM_SYSCommand, "int", $SC_MonitorPower, "int", 2) Case "ON" DllCall("user32.dll", "int", "SendMessage", "hwnd", $HWND, "int", $WM_SYSCommand, "int", $SC_MonitorPower, "int", -1) Case Else MsgBox(32, @ScriptName, "Command usage: on/off") EndSwitch EndFunc Func To_Tray() If TrayItemGetText($hTray_Show_Item) = "Hide" Then GUISetState(@SW_HIDE, $GUI) TrayItemSetText($hTray_Show_Item, "Show") Else GUISetState(@SW_SHOW, $GUI) GUISetState(@SW_RESTORE, $GUI) TrayItemSetText($hTray_Show_Item, "Hide") EndIf EndFunc Func On_Exit() Exit EndFunc |
You also can download precompiled x64 version.
Again, I vaguely understand how it works because never had encountered Windows API, but this script can be compiled into a small application which just sits in system tray and waits for events from laptop lid. In case of such an event it turns display on or off.
This script is imperfect but I was unable to make it better in reasonable time.
The main problem is that it controls all displays simultaneously.
I use dual display configuration usually and on lid close this app is switching off both displays.
I tried to enumerate displays and get a handle of a particular display to make this script more selective, but unfortunately, both methods I found report that I have only one display… May be that is because I use “duplicate mode”/”projector only” – I don’t know.
Attempts to enumerate displays
#include <WinAPIGdi.au3> #include <WinAPIGdi.au3> #include <WindowsConstants.au3> Local $objWMIService = ObjGet("winmgmts:{impersonationLevel=impersonate}!\\" & @ComputerName & "\root\cimv2") Local $colMonitors = $objWMIService.ExecQuery ("Select * from Win32_DesktopMonitor", "WQL", 0x10 + 0x20) If NOT IsObj($colMonitors) Then Exit Local $iCount = 0 For $oMonitor In $colMonitors $iCount += 1 Next ConsoleWrite( "Number of monitors : " & $iCount) ConsoleWrite(@CRLF); $aMonitor = _WinAPI_EnumDisplayMonitors() If @error Then Exit If $aMonitor[0][0] > 1 Then ConsoleWrite("extended mode") Else ConsoleWrite("duplicate mode") EndIf ConsoleWrite(@CRLF); ;------------------------------------ #include <GUIConstantsEx.au3> Global $GUI = GUICreate("Laptop Lid Monitor", 300, 150) GUISetIcon(@SystemDir & "\Shell32.dll", -175) GUISetState() Local $handle=DllCallbackRegister("MonitorEnumProc","int","hwnd;hwnd;ptr;lparam") DllCall("user32.dll","int","EnumDisplayMonitors","HWnd",Null,"ptr",Null,"ptr", DllCallbackGetPtr($handle),"lparam",0) ConsoleWrite(@CRLF); Func MonitorEnumProc($hMonitor,$hdcMonitor,$lprect,$lparam) ;MsgBox(0,"Monitor","Monitor handle: "&$hMonitor&@CRLF&"LPARAM: "&$lparam) ConsoleWrite("Monitor handle: "&$hMonitor&"LPARAM: "&$lparam) Return True EndFunc |
So, without the ability to address a particular display this script can’t switch off internal display only. It is also impossible to check periodically and maintain the state of the internal display in case it has been activated by mousemove or something like that.
But in case of a laptop not burdened by external displays, mice etc – this script works pretty well.
Autoit is a great thing.