Turn off a display when closing a lid on a Windows 7 laptop.

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.