Introduction
A couple of months back, the “Social Activity Group” in my company started a campaign whereby it would turn off the monitors if seen not in use. This was really a great initiative to bring down the energy wastage within the company. This motivated me to write a simple application that would do what the group was trying. The best time when a monitor should turn off is when we lock the computer. Hence it makes sense to turn it off as soon as we lock a machine.
The Solution
The solution is simple. Just follow the steps below:
- Capture the “Windows Lock/Unclock” event.
- Capture User Interaction events like Mouse Move, Keyboard hit, so that if monitor gets turned on because of these movements, it could again be turned off.
- Signal the monitor to turn off if the computer is locked.
- Repeat steps 2 and 3 until the machine gets unlocked.
Step 1: Capture the Windows Lock/Unlock event
There are several ways to get an event for Windows Lock and Unlock, of which I chose to go ahead with the Winlogon Notification Package. The Winlogon Notification Package is a DLL which lets you export functions to handle Winlogon.exe events. These event messages includes lock, unlock, logoff, logon, startup, shutdown, startscreensaver, stopscreensaver, and startshell etc.
All you have to do is write a DLL exporting the event handlers and make a few Registry entries. Below is the code snippet from the attached project which has three event handles: “CompLocked
”, “CompUnlocked
”, and “CompLoggedIn
” exported from “ShutDownMonitor.dll” (attached with the code).
HANDLE g_hUnLockEvent = NULL; HANDLE g_hMovementEvent = NULL; VOID CompLocked( PWLX_NOTIFICATION_INFO pInfo )
{
if( !g_hUnLockEvent )
{
g_hUnLockEvent = CreateEvent( NULL, TRUE, FALSE, _T("MonitorShutDown") );
}
else
{
ResetEvent( g_hUnLockEvent );
}
if( !g_hMovementEvent )
{
g_hMovementEvent = CreateEvent(NULL, TRUE, TRUE, _T("MovementCaptured"));
}
else
{
SetEvent( g_hMovementEvent );
}
CreateThread( NULL, 0, ShutDownMonitor, NULL, 0, NULL );
}
VOID CompUnlocked( PWLX_NOTIFICATION_INFO pInfo )
{
if( g_hUnLockEvent )
{
SetEvent( g_hUnLockEvent );
}
}
VOID CompLoggedIn( PWLX_NOTIFICATION_INFO pInfo )
{
if( g_hUnLockEvent )
{
SetEvent( g_hUnLockEvent );
}
}
Step 2: Capture User Interaction events like Mouse Move, Keyboard hit
This can be easily achieved using SetWindowsHookEx
. You will have to set a hook for WH_MOUSE
and WH_KEYBOARD
to capture mouse and keyboard events. The calls would look like:
SetWindowsHookEx( WH_KEYBOARD, KeyboardProc, hModule, 0 );
SetWindowsHookEx( WH_MOUSE, MouseProc, hModule, 0 );
KeyboardProc
: The hook procedure for keyboard events.MouseProc
: The hook procedure for mouse events.hModule
: Handle of the module that implements KeyboardProc
and MouseProc
.
I implemented these functions right in my Winlogon Notification Package, i.e., “ShutDownMonitor.dll”, and the implementation looks like:
LRESULT CALLBACK MouseProc( int nCode, WPARAM wParam, LPARAM lParam )
{
static POINT ptOld = reinterpret_cast<PMOUSEHOOKSTRUCT>( lParam )->pt;
POINT ptNew = reinterpret_cast<PMOUSEHOOKSTRUCT>( lParam )->pt;
int nXDisp = abs( ptNew.x - ptOld.x );
int nYDisp = abs( ptNew.y - ptOld.y );
if( ( WAIT_OBJECT_0 != WaitForSingleObject( g_hMovementEvent, 0 ) ) &&
( nXDisp || nYDisp ) )
{
EnterCriticalSection( &g_csMovement );
SetEvent( g_hMovementEvent );
LeaveCriticalSection( &g_csMovement );
}
ptOld = ptNew;
return CallNextHookEx( g_hMouseHook, nCode, wParam, lParam );
}
LRESULT CALLBACK KeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{
if( WAIT_OBJECT_0 != WaitForSingleObject( g_hMovementEvent, 0 ) )
{
if( (lParam & 0x80000000) && (lParam & 0x40000000) )
{
EnterCriticalSection( &g_csMovement );
SetEvent( g_hMovementEvent );
LeaveCriticalSection( &g_csMovement );
}
}
return CallNextHookEx( g_hKBHook, nCode, wParam, lParam );
}
Step 3: Signal the monitor to turn off if the computer is locked
Turning the monitor on/off can be done using SendMessage()
:
const int ON = -1;
const int OFF = 2;
const int STANBY = 1;
SendMessage( hWnd, WM_SYSCOMMAND, SC_MONITORPOWER, OFF);
where hWnd
is the handle to the topmost window. When the computer is locked, the topmost window has the caption “Computer Locked”. So, our task is to find this window and send it a message to power off the monitor. Thus, the “ShutDownMonitor
” thread used in step 1 goes like this:
DWORD WINAPI ShutDownMonitor( LPVOID )
{
DWORD dwDisposition = 0;
HKEY hKey = NULL;
DWORD dwSleepTime = 10;
InitializeCriticalSection( &g_csMovement );
HMODULE hModule = LoadLibrary( _T("ShutDownMonitor.dll") );
typedef LRESULT (CALLBACK *HOOKPROC)( int nCode, WPARAM wParam, LPARAM lParam );
HOOKPROC MouseProc = NULL;
HOOKPROC KeyboardProc = NULL;
if( hModule )
{
MouseProc = (HOOKPROC)GetProcAddress( hModule, "MouseProc" );
KeyboardProc = (HOOKPROC)GetProcAddress( hModule, "KeyboardProc" );
}
if( MouseProc )
{
g_hMouseHook = ::SetWindowsHookEx( WH_MOUSE, MouseProc, hModule, 0 );
}
if( KeyboardProc )
{
g_hKBHook = ::SetWindowsHookEx( WH_KEYBOARD, KeyboardProc, hModule, 0 );
}
HWND hWnd = NULL;
while( WAIT_TIMEOUT == WaitForSingleObject( g_hUnLockEvent, 500 ) )
{
hWnd = FindWindow( NULL, _T("Computer Locked") );
if( hWnd )
{
break;
}
}
do
{
hWnd = FindWindow( NULL, _T("Computer Locked") );
if( hWnd )
{
RECT rectWnd = {0};
GetWindowRect( hWnd, &rectWnd );
int x = rectWnd.left + ( rectWnd.right - rectWnd.left )/2;
int y = rectWnd.top + ( rectWnd.bottom - rectWnd.top )/2;
SetCursorPos( x, y );
::SendMessage( hWnd, WM_SYSCOMMAND, SC_MONITORPOWER, OFF );
}
EnterCriticalSection( &g_csMovement );
ResetEvent( g_hMovementEvent );
LeaveCriticalSection( &g_csMovement );
WaitForSingleObject( g_hMovementEvent, INFINITE );
if( dwSleepTime < 60 )
{
dwSleepTime *= 2;
}
}while( WAIT_TIMEOUT == WaitForSingleObject( g_hUnLockEvent, dwSleepTime*1000 ) );
if( g_hKBHook )
{
UnhookWindowsHookEx( g_hKBHook );
g_hKBHook = NULL;
}
if( g_hMouseHook )
{
UnhookWindowsHookEx( g_hMouseHook );
g_hMouseHook = NULL;
}
DeleteCriticalSection( &g_csMovement );
return 0;
}
Installing the utility
In order to have this utility installed on the machine, copy “ShutDownMonitor.dll” into c:\windows\System32 and make the following Registry changes:
HKEY_LOCAL_MACHINE
\Software
\Microsoft
\Windows NT
\CurrentVersion
\Winlogon
\Notify
\ShutDownMonitor
\Asynchronous REG_DWORD 1
\DllName REG_SZ ShutDownMonitor.dll
\Lock REG_SZ CompLocked
\Logon REG_SZ CompLoggedIn
\Unlock REG_SZ CompUnlocked
After the Registry changes are done, you will have to restart the machine to get the utility into effect. That’s it, you are all set to save precious energy :)
And, if you want to uninstall the utility, first delete the Registry changes, restart the machine, and then delete the file “ShutDownMonitor.dll” from c:\windows\system32.
References