Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
614 views
in Technique[技术] by (71.8m points)

winapi - Hook system power button in Windows

I have a headless computer running a custom service that I want to enable/disable using the power button, rather than having to remotely connect every time. The computer does other things as well, so turning it off is not an option.

Is it possible to hook the system power button under Windows XP & up, such that my program would get the event before Windows initiates a powerdown/sleep event (before PBT_APMQUERYSUSPEND event get sent out)?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This is indeed possible, but it's a bit hackish and requires two completely different implementations depending on the Windows version. For both methods, you need to set your power button to put the computer to sleep in your power options.

Windows XP and below:

You need to overwrite your program's main window's WndProc function. On IDEs that don't support this natively, this can be done using SetWindowLong in user32 API. In your custom WndProc function, listen for a WM_POWERBROADCAST (0x218) message. If you receive a message with wParam of PBT_APMQUERYSUSPEND (0x0), call your wanted function and then return BROADCAST_QUERY_DENY (0x424D5144) instead of calling the base WndProc function. Example code:

//At program start
//GWL_WNDPROC = -4
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_APMQUERYSUSPEND = 0x0
//BROADCAST_QUERY_DENY = 0x424D5144
if wMsg = WM_POWERBROADCAST && wParam = PBT_APMQUERYSUSPEND (
    //CALL YOUR FUNCTION HERE!
    return BROADCAST_QUERY_DENY
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)

Windows Vista & up: (thanks to Remy Lebeau for setting me on the right track)

You need to override WndProc like for XP, but also call SetThreadExecutionState in kernel32 API to disable sleep mode and RegisterPowerSettingNotification in user32 API to listen for advanced power notifications. You will be listening, in particular, to the GUID_SYSTEM_AWAYMODE notification, which gets sent out when the system was asked to go to sleep but is unable to do so. To easily convert a string to a properly formed LPCGUID you can use UuidFromStringA in rpcrt4.dll API. Example code:

typedef struct UUID{
    int d1, d2, d3, d4
} LPCGUID;

//At program start
//GWL_WNDPROC = -4
//ES_CONTINUOUS = 0x80000000
//ES_SYSTEM_REQUIRED = 0x1
//ES_AWAYMODE_REQUIRED = 0x40
//GUID_SYSTEM_AWAYMODE = "98a7f580-01f7-48aa-9c0f-44352c29e5C0"
LPCGUID uid;
oldWndProc = SetWindowLong(this.hWnd, GWL_WNDPROC, &MyWndProc)
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_AWAYMODE_REQUIRED)
UuidFromStringA(*(GUID_SYSTEM_AWAYMODE), uid)
ps = RegisterPowerSettingNotification(this.hWnd, uid, 0)

//In MyWndProc(hWnd, wMsg, wParam, lParam)
//WM_POWERBROADCAST = 0x218
//PBT_POWERSETTINGCHANGE = 0x8013
if wMsg = WM_POWERBROADCAST && wParam = PBT_POWERSETTINGCHANGE (
    //CALL YOUR FUNCTION HERE!
    //You can additionally extract data from the lParam to verify
    //this is the notification you're waiting for (see below)
)
return CallWindowProc(oldWndProc, hWnd, wMsg, wParam, lParam)

//Before exiting
SetWindowLong(Me.hWnd, GWL_WNDPROC, oldWndProc)
UnregisterPowerSettingNotification(ps)

This method has the side effect of turning off your physical screen (not a problem on a headless machine), and also possibly locking your session. Make sure you disable prompting for password after sleeping to avoid this. There's some additional useful information on RegisterPowerSettingNotification available here which shows how to extract information from the lParam in your WndProc function in case you want additional info on the notification. Have fun ;)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...