Tuesday, March 4, 2008

Turning monitor off - my WinAPI experience

Having some free-of-tasks time at my job I decided to create a simple utility which can turn off the my laptop display. I needed such a tool because sometimes display light is very annoying - for example when you are high and want just to listen for a music. It annoys me with ilumination even with "Blank screen" screensaver.

Of course there are plenty of such a soft available on the Internet (however it is all shareware), but as I already have said I had a free time at my job and strong interest to low-level programming (it is really low-level comparing to my everyday tasks).

Here is requirements for utility I need:
- It must turn off the display when launched
- It must turn it on back when ESC key is pressed

Very simple isn't it?

I have no problems with turning display on/off:

//Turn off
SendMessage(HWND_TOPMOST, WM_SYSCOMMAND, SC_MONITORPOWER, 2);
//Turn on
SendMessage(HWND_TOPMOST, WM_SYSCOMMAND, SC_MONITORPOWER, -1);

Btw, as the window handle you can pass any valid handle

The only problem I had was Windows hooks. Some experience gathered
- If you want to register global hook you MUST define its function in separate DLL
- When registering global hook (registration code can be in the hook DLL or in the calling process, it doesn't matter) use the following code:
SetWindowsHookEx(WH_KEYBOARD, HookFunction, hDLLInstance, 0);
- If you want to store any data in the DLL process namespace think if it must be shared between all your-DLL-and-process connections - because Windows allocates separate memory space for DLL data for each process that uses this DLL. For example, in my case, as DLL contained system-wide keyboard hook, it will have separate space
for data for each process where hook event occured. But, I need to store HWND variable for my utility hidden window - to send WM_DISPOSE event to it when user press ESC key (it is the best way I know to close application. May be there are better ways, simply I don't know them). So the solution will be to store this HWND value in shared DLL memory space - in this case it doesn't matter which process will "execute" the hook - HWND value will be the same for all the DLL instances.
Here is code example how to define shared-variable section:

//Will store main window handle at shared DLL memory space
#pragma data_seg(".SHARED")
HWND hWnd = NULL;
//hook management
HHOOK hhookKeyboardHook;
#pragma data_seg()
#pragma comment(linker, "/section:.SHARED,rws")

And here is the hook function code:

LRESULT __stdcall HookFunction(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode < 0){ // do not process message
return CallNextHookEx((HHOOK)HookFunction, nCode, wParam, lParam);
} else if (VK_ESCAPE == wParam){
//Turn on the monitor
SendMessage(HWND_TOPMOST, WM_SYSCOMMAND, SC_MONITORPOWER, -1);

//Release hook
if(NULL != hhookKeyboardHook ){
UnhookWindowsHookEx(hhookKeyboardHook );
}

//Send message to close application
SendMessage(hWnd, WM_DESTROY, 0, 0);
}

//calling next hook which may exist
return CallNextHookEx((HHOOK)HookFunction, nCode, wParam, lParam);
}

And code which sets the hook:

__declspec(dllexport) void __stdcall SetHook(HWND windowHandle){
hWnd = windowHandle;
SendMessage(HWND_TOPMOST, WM_SYSCOMMAND, SC_MONITORPOWER, 2);
SetWindowsHookEx(WH_KEYBOARD, HookFunction, hInst, 0);
}

Both functions are located at separate DLL, SetHook is marked as __declspec(dllexport) to be able to dynamically call it from dynamically loaded DLL library. I used Microsoft Visual Studio 2003 to work on this problem.

1 comment:

Anonymous said...

Well said.